diff --git a/dbms/src/DataTypes/DataTypeInterval.cpp b/dbms/src/DataTypes/DataTypeInterval.cpp index ab2993b884a..c7ee3ede334 100644 --- a/dbms/src/DataTypes/DataTypeInterval.cpp +++ b/dbms/src/DataTypes/DataTypeInterval.cpp @@ -19,6 +19,7 @@ void registerDataTypeInterval(DataTypeFactory & factory) factory.registerSimpleDataType("IntervalDay", [] { return DataTypePtr(std::make_shared(DataTypeInterval::Day)); }); factory.registerSimpleDataType("IntervalWeek", [] { return DataTypePtr(std::make_shared(DataTypeInterval::Week)); }); factory.registerSimpleDataType("IntervalMonth", [] { return DataTypePtr(std::make_shared(DataTypeInterval::Month)); }); + factory.registerSimpleDataType("IntervalQuarter", [] { return DataTypePtr(std::make_shared(DataTypeInterval::Quarter)); }); factory.registerSimpleDataType("IntervalYear", [] { return DataTypePtr(std::make_shared(DataTypeInterval::Year)); }); } diff --git a/dbms/src/DataTypes/DataTypeInterval.h b/dbms/src/DataTypes/DataTypeInterval.h index afbcf2d6a45..6f4f08c16c0 100644 --- a/dbms/src/DataTypes/DataTypeInterval.h +++ b/dbms/src/DataTypes/DataTypeInterval.h @@ -25,6 +25,7 @@ public: Day, Week, Month, + Quarter, Year }; @@ -46,6 +47,7 @@ public: case Day: return "Day"; case Week: return "Week"; case Month: return "Month"; + case Quarter: return "Quarter"; case Year: return "Year"; default: __builtin_unreachable(); } diff --git a/dbms/src/Functions/FunctionDateOrDateTimeAddInterval.h b/dbms/src/Functions/FunctionDateOrDateTimeAddInterval.h index c4b7639908f..9b27282ec19 100644 --- a/dbms/src/Functions/FunctionDateOrDateTimeAddInterval.h +++ b/dbms/src/Functions/FunctionDateOrDateTimeAddInterval.h @@ -113,6 +113,21 @@ struct AddMonthsImpl } }; +struct AddQuartersImpl +{ + static constexpr auto name = "addQuarters"; + + static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone) + { + return time_zone.addQuarters(t, delta); + } + + static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone) + { + return time_zone.addQuarters(DayNum(d), delta); + } +}; + struct AddYearsImpl { static constexpr auto name = "addYears"; @@ -149,6 +164,7 @@ struct SubtractHoursImpl : SubtractIntervalImpl { static constexpr struct SubtractDaysImpl : SubtractIntervalImpl { static constexpr auto name = "subtractDays"; }; struct SubtractWeeksImpl : SubtractIntervalImpl { static constexpr auto name = "subtractWeeks"; }; struct SubtractMonthsImpl : SubtractIntervalImpl { static constexpr auto name = "subtractMonths"; }; +struct SubtractQuartersImpl : SubtractIntervalImpl { static constexpr auto name = "subtractQuarters"; }; struct SubtractYearsImpl : SubtractIntervalImpl { static constexpr auto name = "subtractYears"; }; diff --git a/dbms/src/Functions/FunctionsConversion.cpp b/dbms/src/Functions/FunctionsConversion.cpp index fdfc153f594..a83a756010c 100644 --- a/dbms/src/Functions/FunctionsConversion.cpp +++ b/dbms/src/Functions/FunctionsConversion.cpp @@ -89,6 +89,7 @@ void registerFunctionsConversion(FunctionFactory & factory) factory.registerFunction>(); factory.registerFunction>(); factory.registerFunction>(); + factory.registerFunction>(); factory.registerFunction>(); } diff --git a/dbms/src/Functions/FunctionsConversion.h b/dbms/src/Functions/FunctionsConversion.h index 1428fec4f48..6b42bec10df 100644 --- a/dbms/src/Functions/FunctionsConversion.h +++ b/dbms/src/Functions/FunctionsConversion.h @@ -738,6 +738,7 @@ DEFINE_NAME_TO_INTERVAL(Hour) DEFINE_NAME_TO_INTERVAL(Day) DEFINE_NAME_TO_INTERVAL(Week) DEFINE_NAME_TO_INTERVAL(Month) +DEFINE_NAME_TO_INTERVAL(Quarter) DEFINE_NAME_TO_INTERVAL(Year) #undef DEFINE_NAME_TO_INTERVAL diff --git a/dbms/src/Functions/FunctionsStringSearch.cpp b/dbms/src/Functions/FunctionsStringSearch.cpp index 337edbbc168..af7ea515f4e 100644 --- a/dbms/src/Functions/FunctionsStringSearch.cpp +++ b/dbms/src/Functions/FunctionsStringSearch.cpp @@ -1080,7 +1080,7 @@ void registerFunctionsStringSearch(FunctionFactory & factory) factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); - factory.registerFunction(); + factory.registerFunction(FunctionFactory::CaseInsensitive); factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); diff --git a/dbms/src/Functions/abs.cpp b/dbms/src/Functions/abs.cpp index e94be787cc4..872c8404176 100644 --- a/dbms/src/Functions/abs.cpp +++ b/dbms/src/Functions/abs.cpp @@ -48,7 +48,7 @@ template <> struct FunctionUnaryArithmeticMonotonicity void registerFunctionAbs(FunctionFactory & factory) { - factory.registerFunction(); + factory.registerFunction(FunctionFactory::CaseInsensitive); } } diff --git a/dbms/src/Functions/addQuarters.cpp b/dbms/src/Functions/addQuarters.cpp new file mode 100644 index 00000000000..c37fb5561c8 --- /dev/null +++ b/dbms/src/Functions/addQuarters.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + + +namespace DB +{ + +using FunctionAddQuarters = FunctionDateOrDateTimeAddInterval; + +void registerFunctionAddQuarters(FunctionFactory & factory) +{ + factory.registerFunction(); +} + +} + + diff --git a/dbms/src/Functions/rand.cpp b/dbms/src/Functions/rand.cpp index cd4ced96b7e..333396c1ecd 100644 --- a/dbms/src/Functions/rand.cpp +++ b/dbms/src/Functions/rand.cpp @@ -9,7 +9,7 @@ using FunctionRand = FunctionRandom; void registerFunctionRand(FunctionFactory & factory) { - factory.registerFunction(); + factory.registerFunction(FunctionFactory::CaseInsensitive); } } diff --git a/dbms/src/Functions/regexpQuoteMeta.cpp b/dbms/src/Functions/regexpQuoteMeta.cpp new file mode 100644 index 00000000000..b14a58ba366 --- /dev/null +++ b/dbms/src/Functions/regexpQuoteMeta.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if USE_RE2_ST + #include // Y_IGNORE +#else + #define re2_st re2 +#endif + +namespace DB +{ +using namespace GatherUtils; + +namespace ErrorCodes +{ + extern const int ILLEGAL_COLUMN; + extern const int ILLEGAL_TYPE_OF_ARGUMENT; + extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH; +} + +class FunctionRegexpQuoteMeta : public IFunction +{ +public: + static constexpr auto name = "regexpQuoteMeta"; + + static FunctionPtr create(const Context &) + { + return std::make_shared(); + } + + String getName() const override + { + return name; + } + + size_t getNumberOfArguments() const override + { + return 1; + } + + bool useDefaultImplementationForConstants() const override + { + return true; + } + + DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override + { + if (!WhichDataType(arguments[0].type).isString()) + throw Exception( + "Illegal type " + arguments[0].type->getName() + " of 1 argument of function " + getName() + ". Must be String.", + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + + return std::make_shared(); + } + + void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) override + { + const ColumnPtr column_string = block.getByPosition(arguments[0]).column; + const ColumnString * input = checkAndGetColumn(column_string.get()); + + if (!input) + throw Exception( + "Illegal column " + block.getByPosition(arguments[0]).column->getName() + " of first argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + + auto dst_column = ColumnString::create(); + auto & dst_data = dst_column->getChars(); + auto & dst_offsets = dst_column->getOffsets(); + + dst_data.resize(input->getChars().size() * input->size()); + dst_offsets.resize(input_rows_count); + + const ColumnString::Offsets & src_offsets = input->getOffsets(); + + auto source = reinterpret_cast(input->getChars().data()); + auto dst = reinterpret_cast(dst_data.data()); + auto dst_pos = dst; + + size_t src_offset_prev = 0; + + for (size_t row = 0; row < input_rows_count; ++row) + { + size_t srclen = src_offsets[row] - src_offset_prev - 1; + + /// suboptimal, but uses original implementation from re2 + re2_st::StringPiece unquoted(source, srclen); + const auto & quoted = re2_st::RE2::QuoteMeta(unquoted); + const auto size = quoted.size(); + std::memcpy(dst_pos, quoted.data(), size); + + source += srclen + 1; + dst_pos[size] = '\0'; + dst_pos += size + 1; + + dst_offsets[row] = dst_pos - dst; + src_offset_prev = src_offsets[row]; + } + + dst_data.resize(dst_pos - dst); + + block.getByPosition(result).column = std::move(dst_column); + } + +}; + +void registerFunctionRegexpQuoteMeta(FunctionFactory & factory) +{ + factory.registerFunction(); +} +} diff --git a/dbms/src/Functions/registerFunctionsDateTime.cpp b/dbms/src/Functions/registerFunctionsDateTime.cpp index 3e7f2a6affd..5751fc800a9 100644 --- a/dbms/src/Functions/registerFunctionsDateTime.cpp +++ b/dbms/src/Functions/registerFunctionsDateTime.cpp @@ -47,6 +47,7 @@ void registerFunctionAddHours(FunctionFactory &); void registerFunctionAddDays(FunctionFactory &); void registerFunctionAddWeeks(FunctionFactory &); void registerFunctionAddMonths(FunctionFactory &); +void registerFunctionAddQuarters(FunctionFactory &); void registerFunctionAddYears(FunctionFactory &); void registerFunctionSubtractSeconds(FunctionFactory &); void registerFunctionSubtractMinutes(FunctionFactory &); @@ -54,6 +55,7 @@ void registerFunctionSubtractHours(FunctionFactory &); void registerFunctionSubtractDays(FunctionFactory &); void registerFunctionSubtractWeeks(FunctionFactory &); void registerFunctionSubtractMonths(FunctionFactory &); +void registerFunctionSubtractQuarters(FunctionFactory &); void registerFunctionSubtractYears(FunctionFactory &); void registerFunctionDateDiff(FunctionFactory &); void registerFunctionToTimeZone(FunctionFactory &); @@ -106,6 +108,7 @@ void registerFunctionsDateTime(FunctionFactory & factory) registerFunctionAddDays(factory); registerFunctionAddWeeks(factory); registerFunctionAddMonths(factory); + registerFunctionAddQuarters(factory); registerFunctionAddYears(factory); registerFunctionSubtractSeconds(factory); registerFunctionSubtractMinutes(factory); @@ -113,6 +116,7 @@ void registerFunctionsDateTime(FunctionFactory & factory) registerFunctionSubtractDays(factory); registerFunctionSubtractWeeks(factory); registerFunctionSubtractMonths(factory); + registerFunctionSubtractQuarters(factory); registerFunctionSubtractYears(factory); registerFunctionDateDiff(factory); registerFunctionToTimeZone(factory); diff --git a/dbms/src/Functions/registerFunctionsString.cpp b/dbms/src/Functions/registerFunctionsString.cpp index 3a07d8bbd65..15d37d939b0 100644 --- a/dbms/src/Functions/registerFunctionsString.cpp +++ b/dbms/src/Functions/registerFunctionsString.cpp @@ -21,6 +21,8 @@ void registerFunctionSubstringUTF8(FunctionFactory &); void registerFunctionAppendTrailingCharIfAbsent(FunctionFactory &); void registerFunctionStartsWith(FunctionFactory &); void registerFunctionEndsWith(FunctionFactory &); +void registerFunctionTrim(FunctionFactory &); +void registerFunctionRegexpQuoteMeta(FunctionFactory &); #if USE_BASE64 void registerFunctionBase64Encode(FunctionFactory &); @@ -46,6 +48,8 @@ void registerFunctionsString(FunctionFactory & factory) registerFunctionAppendTrailingCharIfAbsent(factory); registerFunctionStartsWith(factory); registerFunctionEndsWith(factory); + registerFunctionTrim(factory); + registerFunctionRegexpQuoteMeta(factory); #if USE_BASE64 registerFunctionBase64Encode(factory); registerFunctionBase64Decode(factory); diff --git a/dbms/src/Functions/reverse.cpp b/dbms/src/Functions/reverse.cpp index 065e1d28073..b7447a7882b 100644 --- a/dbms/src/Functions/reverse.cpp +++ b/dbms/src/Functions/reverse.cpp @@ -147,7 +147,7 @@ private: void registerFunctionReverse(FunctionFactory & factory) { - factory.registerFunction(); + factory.registerFunction(FunctionFactory::CaseInsensitive); } } diff --git a/dbms/src/Functions/subtractQuarters.cpp b/dbms/src/Functions/subtractQuarters.cpp new file mode 100644 index 00000000000..6c066ed17a1 --- /dev/null +++ b/dbms/src/Functions/subtractQuarters.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + + +namespace DB +{ + +using FunctionSubtractQuarters = FunctionDateOrDateTimeAddInterval; + +void registerFunctionSubtractQuarters(FunctionFactory & factory) +{ + factory.registerFunction(); +} + +} + + diff --git a/dbms/src/Functions/trim.cpp b/dbms/src/Functions/trim.cpp new file mode 100644 index 00000000000..42aa59bffe5 --- /dev/null +++ b/dbms/src/Functions/trim.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +#if __SSE4_2__ +#include +#endif + +namespace DB +{ + +namespace ErrorCodes +{ + extern const int ILLEGAL_TYPE_OF_ARGUMENT; +} + +struct TrimModeLeft +{ + static constexpr auto name = "trimLeft"; + static constexpr bool trim_left = true; + static constexpr bool trim_right = false; +}; + +struct TrimModeRight +{ + static constexpr auto name = "trimRight"; + static constexpr bool trim_left = false; + static constexpr bool trim_right = true; +}; + +struct TrimModeBoth +{ + static constexpr auto name = "trimBoth"; + static constexpr bool trim_left = true; + static constexpr bool trim_right = true; +}; + +template +class FunctionTrimImpl +{ +public: + static void vector( + const ColumnString::Chars & data, + const ColumnString::Offsets & offsets, + ColumnString::Chars & res_data, + ColumnString::Offsets & res_offsets) + { + size_t size = offsets.size(); + res_offsets.resize(size); + res_data.reserve(data.size()); + + size_t prev_offset = 0; + size_t res_offset = 0; + + const UInt8 * start; + size_t length; + + for (size_t i = 0; i < size; ++i) + { + execute(reinterpret_cast(&data[prev_offset]), offsets[i] - prev_offset - 1, start, length); + + res_data.resize(res_data.size() + length + 1); + std::memcpy(&res_data[res_offset], start, length); + res_offset += length + 1; + res_data[res_offset - 1] = '\0'; + + res_offsets[i] = res_offset; + prev_offset = offsets[i]; + } + } + + static void vector_fixed(const ColumnString::Chars &, size_t, ColumnString::Chars &) + { + throw Exception("Functions trimLeft, trimRight and trimBoth cannot work with FixedString argument", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); + } + +private: + static void execute(const UInt8 * data, size_t size, const UInt8 *& res_data, size_t & res_size) + { + size_t chars_to_trim_left = 0; + size_t chars_to_trim_right = 0; + char whitespace = ' '; +#if __SSE4_2__ + const auto bytes_sse = sizeof(__m128i); + const auto size_sse = size - (size % bytes_sse); + const auto whitespace_mask = _mm_set1_epi8(whitespace); + constexpr auto base_sse_mode = _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_EACH | _SIDD_NEGATIVE_POLARITY; + auto mask = bytes_sse; +#endif + + if constexpr (mode::trim_left) + { +#if __SSE4_2__ + /// skip whitespace from left in blocks of up to 16 characters + constexpr auto left_sse_mode = base_sse_mode | _SIDD_LEAST_SIGNIFICANT; + while (mask == bytes_sse && chars_to_trim_left < size_sse) + { + const auto chars = _mm_loadu_si128(reinterpret_cast(data + chars_to_trim_left)); + mask = _mm_cmpistri(whitespace_mask, chars, left_sse_mode); + chars_to_trim_left += mask; + } +#endif + /// skip remaining whitespace from left, character by character + while (chars_to_trim_left < size && data[chars_to_trim_left] == whitespace) + ++chars_to_trim_left; + } + + if constexpr (mode::trim_right) + { + constexpr auto right_sse_mode = base_sse_mode | _SIDD_MOST_SIGNIFICANT; + const auto trim_right_size = size - chars_to_trim_left; +#if __SSE4_2__ + /// try to skip whitespace from right in blocks of up to 16 characters + const auto trim_right_size_sse = trim_right_size - (trim_right_size % bytes_sse); + while (mask == bytes_sse && chars_to_trim_right < trim_right_size_sse) + { + const auto chars = _mm_loadu_si128(reinterpret_cast(data + size - chars_to_trim_right - bytes_sse)); + mask = _mm_cmpistri(whitespace_mask, chars, right_sse_mode); + chars_to_trim_right += mask; + } +#endif + /// skip remaining whitespace from right, character by character + while (chars_to_trim_right < trim_right_size && data[size - chars_to_trim_right - 1] == whitespace) + ++chars_to_trim_right; + } + + res_data = data + chars_to_trim_left; + res_size = size - chars_to_trim_left - chars_to_trim_right; + } +}; + +using FunctionTrimLeft = FunctionStringToString, TrimModeLeft>; +using FunctionTrimRight = FunctionStringToString, TrimModeRight>; +using FunctionTrimBoth = FunctionStringToString, TrimModeBoth>; + +void registerFunctionTrim(FunctionFactory & factory) +{ + factory.registerFunction(); + factory.registerFunction(); + factory.registerFunction(); +} +} diff --git a/dbms/src/Parsers/CommonParsers.h b/dbms/src/Parsers/CommonParsers.h index 414f4ceccbc..44c8ab17fb7 100644 --- a/dbms/src/Parsers/CommonParsers.h +++ b/dbms/src/Parsers/CommonParsers.h @@ -46,4 +46,98 @@ protected: } }; +class ParserInterval: public IParserBase +{ +public: + enum class IntervalKind + { + Incorrect, + Second, + Minute, + Hour, + Day, + Week, + Month, + Quarter, + Year + }; + + IntervalKind interval_kind; + + ParserInterval() : interval_kind(IntervalKind::Incorrect) {} + + const char * getToIntervalKindFunctionName() + { + switch (interval_kind) + { + case ParserInterval::IntervalKind::Second: + return "toIntervalSecond"; + case ParserInterval::IntervalKind::Minute: + return "toIntervalMinute"; + case ParserInterval::IntervalKind::Hour: + return "toIntervalHour"; + case ParserInterval::IntervalKind::Day: + return "toIntervalDay"; + case ParserInterval::IntervalKind::Week: + return "toIntervalWeek"; + case ParserInterval::IntervalKind::Month: + return "toIntervalMonth"; + case ParserInterval::IntervalKind::Quarter: + return "toIntervalQuarter"; + case ParserInterval::IntervalKind::Year: + return "toIntervalYear"; + default: + return nullptr; + } + } + +protected: + const char * getName() const override { return "interval"; } + + bool parseImpl(Pos & pos, ASTPtr & /*node*/, Expected & expected) override + { + if (ParserKeyword("SECOND").ignore(pos, expected) || ParserKeyword("SQL_TSI_SECOND").ignore(pos, expected) + || ParserKeyword("SS").ignore(pos, expected) || ParserKeyword("S").ignore(pos, expected)) + interval_kind = IntervalKind::Second; + else if ( + ParserKeyword("MINUTE").ignore(pos, expected) || ParserKeyword("SQL_TSI_MINUTE").ignore(pos, expected) + || ParserKeyword("MI").ignore(pos, expected) || ParserKeyword("N").ignore(pos, expected)) + interval_kind = IntervalKind::Minute; + else if ( + ParserKeyword("HOUR").ignore(pos, expected) || ParserKeyword("SQL_TSI_HOUR").ignore(pos, expected) + || ParserKeyword("HH").ignore(pos, expected)) + interval_kind = IntervalKind::Hour; + else if ( + ParserKeyword("DAY").ignore(pos, expected) || ParserKeyword("SQL_TSI_DAY").ignore(pos, expected) + || ParserKeyword("DD").ignore(pos, expected) || ParserKeyword("D").ignore(pos, expected)) + interval_kind = IntervalKind::Day; + else if ( + ParserKeyword("WEEK").ignore(pos, expected) || ParserKeyword("SQL_TSI_WEEK").ignore(pos, expected) + || ParserKeyword("WK").ignore(pos, expected) || ParserKeyword("WW").ignore(pos, expected)) + interval_kind = IntervalKind::Week; + else if ( + ParserKeyword("MONTH").ignore(pos, expected) || ParserKeyword("SQL_TSI_MONTH").ignore(pos, expected) + || ParserKeyword("MM").ignore(pos, expected) || ParserKeyword("M").ignore(pos, expected)) + interval_kind = IntervalKind::Month; + else if ( + ParserKeyword("QUARTER").ignore(pos, expected) || ParserKeyword("SQL_TSI_QUARTER").ignore(pos, expected) + || ParserKeyword("QQ").ignore(pos, expected) || ParserKeyword("Q").ignore(pos, expected)) + interval_kind = IntervalKind::Quarter; + else if ( + ParserKeyword("YEAR").ignore(pos, expected) || ParserKeyword("SQL_TSI_YEAR").ignore(pos, expected) + || ParserKeyword("YYYY").ignore(pos, expected) || ParserKeyword("YY").ignore(pos, expected)) + interval_kind = IntervalKind::Year; + else + interval_kind = IntervalKind::Incorrect; + + if (interval_kind == IntervalKind::Incorrect) + { + expected.add(pos, "YEAR, QUARTER, MONTH, WEEK, DAY, HOUR, MINUTE or SECOND"); + return false; + } + /// one of ParserKeyword already made ++pos + return true; + } +}; + } diff --git a/dbms/src/Parsers/ExpressionElementParsers.cpp b/dbms/src/Parsers/ExpressionElementParsers.cpp index 0912d2a5b7b..d01dfd1b7a8 100644 --- a/dbms/src/Parsers/ExpressionElementParsers.cpp +++ b/dbms/src/Parsers/ExpressionElementParsers.cpp @@ -388,6 +388,255 @@ bool ParserSubstringExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & e return true; } +bool ParserTrimExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + /// Handles all possible TRIM/LTRIM/RTRIM call variants + + std::string func_name; + bool trim_left = false; + bool trim_right = false; + bool char_override = false; + ASTPtr expr_node; + ASTPtr pattern_node; + ASTPtr to_remove; + + if (ParserKeyword("LTRIM").ignore(pos, expected)) + { + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + trim_left = true; + } + else if (ParserKeyword("RTRIM").ignore(pos, expected)) + { + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + trim_right = true; + } + else if (ParserKeyword("TRIM").ignore(pos, expected)) + { + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + + if (ParserKeyword("BOTH").ignore(pos, expected)) + { + trim_left = true; + trim_right = true; + char_override = true; + } + else if (ParserKeyword("LEADING").ignore(pos, expected)) + { + trim_left = true; + char_override = true; + } + else if (ParserKeyword("TRAILING").ignore(pos, expected)) + { + trim_right = true; + char_override = true; + } + else + { + trim_left = true; + trim_right = true; + } + + if (char_override) + { + if (!ParserExpression().parse(pos, to_remove, expected)) + return false; + if (!ParserKeyword("FROM").ignore(pos, expected)) + return false; + + auto quote_meta_func_node = std::make_shared(); + auto quote_meta_list_args = std::make_shared(); + quote_meta_list_args->children = {to_remove}; + + quote_meta_func_node->name = "regexpQuoteMeta"; + quote_meta_func_node->arguments = std::move(quote_meta_list_args); + quote_meta_func_node->children.push_back(quote_meta_func_node->arguments); + + to_remove = std::move(quote_meta_func_node); + } + } + + if (!(trim_left || trim_right)) + return false; + + if (!ParserExpression().parse(pos, expr_node, expected)) + return false; + + if (pos->type != TokenType::ClosingRoundBracket) + return false; + ++pos; + + /// Convert to regexp replace function call + + if (char_override) + { + auto pattern_func_node = std::make_shared(); + auto pattern_list_args = std::make_shared(); + if (trim_left && trim_right) + { + pattern_list_args->children = { + std::make_shared("^["), + to_remove, + std::make_shared("]*|["), + to_remove, + std::make_shared("]*$") + }; + func_name = "replaceRegexpAll"; + } + else + { + if (trim_left) + { + pattern_list_args->children = { + std::make_shared("^["), + to_remove, + std::make_shared("]*") + }; + } + else + { + /// trim_right == false not possible + pattern_list_args->children = { + std::make_shared("["), + to_remove, + std::make_shared("]*$") + }; + } + func_name = "replaceRegexpOne"; + } + + pattern_func_node->name = "concat"; + pattern_func_node->arguments = std::move(pattern_list_args); + pattern_func_node->children.push_back(pattern_func_node->arguments); + + pattern_node = std::move(pattern_func_node); + } + else + { + if (trim_left && trim_right) + { + func_name = "trimBoth"; + } + else + { + if (trim_left) + { + func_name = "trimLeft"; + } + else + { + /// trim_right == false not possible + func_name = "trimRight"; + } + } + } + + auto expr_list_args = std::make_shared(); + if (char_override) + expr_list_args->children = {expr_node, pattern_node, std::make_shared("")}; + else + expr_list_args->children = {expr_node}; + + auto func_node = std::make_shared(); + func_node->name = func_name; + func_node->arguments = std::move(expr_list_args); + func_node->children.push_back(func_node->arguments); + + node = std::move(func_node); + return true; +} + +bool ParserLeftExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + /// Rewrites left(expr, length) to SUBSTRING(expr, 1, length) + + ASTPtr expr_node; + ASTPtr start_node; + ASTPtr length_node; + + if (!ParserKeyword("LEFT").ignore(pos, expected)) + return false; + + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + + if (!ParserExpression().parse(pos, expr_node, expected)) + return false; + + ParserToken(TokenType::Comma).ignore(pos, expected); + + if (!ParserExpression().parse(pos, length_node, expected)) + return false; + + if (pos->type != TokenType::ClosingRoundBracket) + return false; + ++pos; + + auto expr_list_args = std::make_shared(); + start_node = std::make_shared(1); + expr_list_args->children = {expr_node, start_node, length_node}; + + auto func_node = std::make_shared(); + func_node->name = "substring"; + func_node->arguments = std::move(expr_list_args); + func_node->children.push_back(func_node->arguments); + + node = std::move(func_node); + return true; +} + +bool ParserRightExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + /// Rewrites RIGHT(expr, length) to substring(expr, -length) + + ASTPtr expr_node; + ASTPtr length_node; + + if (!ParserKeyword("RIGHT").ignore(pos, expected)) + return false; + + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + + if (!ParserExpression().parse(pos, expr_node, expected)) + return false; + + ParserToken(TokenType::Comma).ignore(pos, expected); + + if (!ParserExpression().parse(pos, length_node, expected)) + return false; + + if (pos->type != TokenType::ClosingRoundBracket) + return false; + ++pos; + + auto start_expr_list_args = std::make_shared(); + start_expr_list_args->children = {length_node}; + + auto start_node = std::make_shared(); + start_node->name = "negate"; + start_node->arguments = std::move(start_expr_list_args); + start_node->children.push_back(start_node->arguments); + + auto expr_list_args = std::make_shared(); + expr_list_args->children = {expr_node, start_node}; + + auto func_node = std::make_shared(); + func_node->name = "substring"; + func_node->arguments = std::move(expr_list_args); + func_node->children.push_back(func_node->arguments); + + node = std::move(func_node); + return true; +} + bool ParserExtractExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) { auto begin = pos; @@ -402,26 +651,42 @@ bool ParserExtractExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & exp ASTPtr expr; const char * function_name = nullptr; - if (ParserKeyword("SECOND").ignore(pos, expected)) - function_name = "toSecond"; - else if (ParserKeyword("MINUTE").ignore(pos, expected)) - function_name = "toMinute"; - else if (ParserKeyword("HOUR").ignore(pos, expected)) - function_name = "toHour"; - else if (ParserKeyword("DAY").ignore(pos, expected)) - function_name = "toDayOfMonth"; - - // TODO: SELECT toRelativeWeekNum(toDate('2017-06-15')) - toRelativeWeekNum(toStartOfYear(toDate('2017-06-15'))) - // else if (ParserKeyword("WEEK").ignore(pos, expected)) - // function_name = "toRelativeWeekNum"; - - else if (ParserKeyword("MONTH").ignore(pos, expected)) - function_name = "toMonth"; - else if (ParserKeyword("YEAR").ignore(pos, expected)) - function_name = "toYear"; - else + ParserInterval interval_parser; + if (!interval_parser.ignore(pos, expected)) return false; + switch (interval_parser.interval_kind) + { + case ParserInterval::IntervalKind::Second: + function_name = "toSecond"; + break; + case ParserInterval::IntervalKind::Minute: + function_name = "toMinute"; + break; + case ParserInterval::IntervalKind::Hour: + function_name = "toHour"; + break; + case ParserInterval::IntervalKind::Day: + function_name = "toDayOfMonth"; + break; + case ParserInterval::IntervalKind::Week: + // TODO: SELECT toRelativeWeekNum(toDate('2017-06-15')) - toRelativeWeekNum(toStartOfYear(toDate('2017-06-15'))) + // else if (ParserKeyword("WEEK").ignore(pos, expected)) + // function_name = "toRelativeWeekNum"; + return false; + case ParserInterval::IntervalKind::Month: + function_name = "toMonth"; + break; + case ParserInterval::IntervalKind::Quarter: + function_name = "toQuarter"; + break; + case ParserInterval::IntervalKind::Year: + function_name = "toYear"; + break; + default: + return false; + } + ParserKeyword s_from("FROM"); if (!s_from.ignore(pos, expected)) return false; @@ -449,6 +714,168 @@ bool ParserExtractExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & exp return true; } +bool ParserDateAddExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + const char * function_name = nullptr; + ASTPtr timestamp_node; + ASTPtr offset_node; + + if (ParserKeyword("DATEADD").ignore(pos, expected) || ParserKeyword("DATE_ADD").ignore(pos, expected) + || ParserKeyword("TIMESTAMPADD").ignore(pos, expected) || ParserKeyword("TIMESTAMP_ADD").ignore(pos, expected)) + function_name = "plus"; + else if (ParserKeyword("DATESUB").ignore(pos, expected) || ParserKeyword("DATE_SUB").ignore(pos, expected) + || ParserKeyword("TIMESTAMPSUB").ignore(pos, expected) || ParserKeyword("TIMESTAMP_SUB").ignore(pos, expected)) + function_name = "minus"; + else + return false; + + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + + ParserInterval interval_parser; + if (interval_parser.ignore(pos, expected)) + { + /// function(unit, offset, timestamp) + if (pos->type != TokenType::Comma) + return false; + ++pos; + + if (!ParserExpression().parse(pos, offset_node, expected)) + return false; + + if (pos->type != TokenType::Comma) + return false; + ++pos; + + if (!ParserExpression().parse(pos, timestamp_node, expected)) + return false; + } + else + { + /// function(timestamp, INTERVAL offset unit) + if (!ParserExpression().parse(pos, timestamp_node, expected)) + return false; + + if (pos->type != TokenType::Comma) + return false; + ++pos; + + if (!ParserKeyword("INTERVAL").ignore(pos, expected)) + return false; + + if (!ParserExpression().parse(pos, offset_node, expected)) + return false; + + interval_parser.ignore(pos, expected); + + } + if (pos->type != TokenType::ClosingRoundBracket) + return false; + ++pos; + + const char * interval_function_name = interval_parser.getToIntervalKindFunctionName(); + + auto interval_expr_list_args = std::make_shared(); + interval_expr_list_args->children = {offset_node}; + + auto interval_func_node = std::make_shared(); + interval_func_node->name = interval_function_name; + interval_func_node->arguments = std::move(interval_expr_list_args); + interval_func_node->children.push_back(interval_func_node->arguments); + + auto expr_list_args = std::make_shared(); + expr_list_args->children = {timestamp_node, interval_func_node}; + + auto func_node = std::make_shared(); + func_node->name = function_name; + func_node->arguments = std::move(expr_list_args); + func_node->children.push_back(func_node->arguments); + + node = std::move(func_node); + + return true; +} + +bool ParserDateDiffExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) +{ + const char * interval_name = nullptr; + ASTPtr left_node; + ASTPtr right_node; + + if (!(ParserKeyword("DATEDIFF").ignore(pos, expected) || ParserKeyword("DATE_DIFF").ignore(pos, expected) + || ParserKeyword("TIMESTAMPDIFF").ignore(pos, expected) || ParserKeyword("TIMESTAMP_DIFF").ignore(pos, expected))) + return false; + + if (pos->type != TokenType::OpeningRoundBracket) + return false; + ++pos; + + ParserInterval interval_parser; + if (!interval_parser.ignore(pos, expected)) + return false; + + switch (interval_parser.interval_kind) + { + case ParserInterval::IntervalKind::Second: + interval_name = "second"; + break; + case ParserInterval::IntervalKind::Minute: + interval_name = "minute"; + break; + case ParserInterval::IntervalKind::Hour: + interval_name = "hour"; + break; + case ParserInterval::IntervalKind::Day: + interval_name = "day"; + break; + case ParserInterval::IntervalKind::Week: + interval_name = "week"; + break; + case ParserInterval::IntervalKind::Month: + interval_name = "month"; + break; + case ParserInterval::IntervalKind::Quarter: + interval_name = "quarter"; + break; + case ParserInterval::IntervalKind::Year: + interval_name = "year"; + break; + default: + return false; + } + + if (pos->type != TokenType::Comma) + return false; + ++pos; + + if (!ParserExpression().parse(pos, left_node, expected)) + return false; + + if (pos->type != TokenType::Comma) + return false; + ++pos; + + if (!ParserExpression().parse(pos, right_node, expected)) + return false; + + if (pos->type != TokenType::ClosingRoundBracket) + return false; + ++pos; + + auto expr_list_args = std::make_shared(); + expr_list_args->children = {std::make_shared(interval_name), left_node, right_node}; + + auto func_node = std::make_shared(); + func_node->name = "dateDiff"; + func_node->arguments = std::move(expr_list_args); + func_node->children.push_back(func_node->arguments); + + node = std::move(func_node); + + return true; +} + bool ParserNull::parseImpl(Pos & pos, ASTPtr & node, Expected & expected) { @@ -750,7 +1177,12 @@ bool ParserExpressionElement::parseImpl(Pos & pos, ASTPtr & node, Expected & exp || ParserLiteral().parse(pos, node, expected) || ParserCastExpression().parse(pos, node, expected) || ParserExtractExpression().parse(pos, node, expected) + || ParserDateAddExpression().parse(pos, node, expected) + || ParserDateDiffExpression().parse(pos, node, expected) || ParserSubstringExpression().parse(pos, node, expected) + || ParserTrimExpression().parse(pos, node, expected) + || ParserLeftExpression().parse(pos, node, expected) + || ParserRightExpression().parse(pos, node, expected) || ParserCase().parse(pos, node, expected) || ParserFunction().parse(pos, node, expected) || ParserQualifiedAsterisk().parse(pos, node, expected) diff --git a/dbms/src/Parsers/ExpressionElementParsers.h b/dbms/src/Parsers/ExpressionElementParsers.h index a52864d97d1..c6afbd171e4 100644 --- a/dbms/src/Parsers/ExpressionElementParsers.h +++ b/dbms/src/Parsers/ExpressionElementParsers.h @@ -103,6 +103,27 @@ protected: bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; }; +class ParserTrimExpression : public IParserBase +{ +protected: + const char * getName() const override { return "TRIM expression"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; + +class ParserLeftExpression : public IParserBase +{ +protected: + const char * getName() const override { return "LEFT expression"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; + +class ParserRightExpression : public IParserBase +{ +protected: + const char * getName() const override { return "RIGHT expression"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; + class ParserExtractExpression : public IParserBase { protected: @@ -110,6 +131,19 @@ protected: bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; }; +class ParserDateAddExpression : public IParserBase +{ +protected: + const char * getName() const override { return "DATE_ADD expression"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; + +class ParserDateDiffExpression : public IParserBase +{ +protected: + const char * getName() const override { return "DATE_DIFF expression"; } + bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override; +}; /** NULL literal. */ diff --git a/dbms/src/Parsers/ExpressionListParsers.cpp b/dbms/src/Parsers/ExpressionListParsers.cpp index ef75267cffe..de6fc2dc129 100644 --- a/dbms/src/Parsers/ExpressionListParsers.cpp +++ b/dbms/src/Parsers/ExpressionListParsers.cpp @@ -607,25 +607,13 @@ bool ParserIntervalOperatorExpression::parseImpl(Pos & pos, ASTPtr & node, Expec if (!ParserExpressionWithOptionalAlias(false).parse(pos, expr, expected)) return false; - const char * function_name = nullptr; - if (ParserKeyword("SECOND").ignore(pos, expected)) - function_name = "toIntervalSecond"; - else if (ParserKeyword("MINUTE").ignore(pos, expected)) - function_name = "toIntervalMinute"; - else if (ParserKeyword("HOUR").ignore(pos, expected)) - function_name = "toIntervalHour"; - else if (ParserKeyword("DAY").ignore(pos, expected)) - function_name = "toIntervalDay"; - else if (ParserKeyword("WEEK").ignore(pos, expected)) - function_name = "toIntervalWeek"; - else if (ParserKeyword("MONTH").ignore(pos, expected)) - function_name = "toIntervalMonth"; - else if (ParserKeyword("YEAR").ignore(pos, expected)) - function_name = "toIntervalYear"; - else + ParserInterval interval_parser; + if (!interval_parser.ignore(pos, expected)) return false; + const char * function_name = interval_parser.getToIntervalKindFunctionName(); + /// the function corresponding to the operator auto function = std::make_shared(); diff --git a/dbms/tests/performance/right/right.xml b/dbms/tests/performance/right/right.xml new file mode 100644 index 00000000000..7622210133f --- /dev/null +++ b/dbms/tests/performance/right/right.xml @@ -0,0 +1,34 @@ + + right + loop + + + hits_100m_single + + + + + 10000 + + + 5000 + 20000 + + + + + + + + + + func + + right(URL, 16) + substring(URL, greatest(minus(plus(length(URL), 1), 16), 1)) + + + + + SELECT count() FROM hits_100m_single WHERE NOT ignore({func}) + diff --git a/dbms/tests/performance/trim/trim_numbers.xml b/dbms/tests/performance/trim/trim_numbers.xml new file mode 100644 index 00000000000..07587c024ac --- /dev/null +++ b/dbms/tests/performance/trim/trim_numbers.xml @@ -0,0 +1,34 @@ + + trim_numbers + loop + + + + 10000 + + + 5000 + 20000 + + + + + + + + + + func + + trim( + ltrim( + rtrim( + trim(LEADING '012345' FROM + trim(TRAILING '012345' FROM + trim(BOTH '012345' FROM + + + + + SELECT count() FROM numbers(10000000) WHERE NOT ignore({func}toString(number))) + diff --git a/dbms/tests/performance/trim/trim_urls.xml b/dbms/tests/performance/trim/trim_urls.xml new file mode 100644 index 00000000000..3687068f086 --- /dev/null +++ b/dbms/tests/performance/trim/trim_urls.xml @@ -0,0 +1,38 @@ + + trim_urls + loop + + + hits_100m_single + + + + + 10000 + + + 5000 + 20000 + + + + + + + + + + func + + trim( + ltrim( + rtrim( + trim(LEADING 'htpsw:/' FROM + trim(TRAILING '/' FROM + trim(BOTH 'htpsw:/' FROM + + + + + SELECT count() FROM hits_100m_single WHERE NOT ignore({func}URL)) + diff --git a/dbms/tests/performance/trim/trim_whitespace.xml b/dbms/tests/performance/trim/trim_whitespace.xml new file mode 100644 index 00000000000..d7fc5d967a6 --- /dev/null +++ b/dbms/tests/performance/trim/trim_whitespace.xml @@ -0,0 +1,35 @@ + + trim_whitespaces + loop + + + whitespaces + + + + + 30000 + + + + + + + + + + func + + value + trimLeft(value) + trimRight(value) + trimBoth(value) + replaceRegexpOne(value, '^ *', '') + replaceRegexpOne(value, ' *$', '') + replaceRegexpAll(value, '^ *| *$', '') + + + + + SELECT count() FROM whitespaces WHERE NOT ignore({func}) + diff --git a/dbms/tests/performance/trim/whitespaces.sql b/dbms/tests/performance/trim/whitespaces.sql new file mode 100644 index 00000000000..653bd2e7a5a --- /dev/null +++ b/dbms/tests/performance/trim/whitespaces.sql @@ -0,0 +1,17 @@ +CREATE TABLE whitespaces +( + value String +) +ENGINE = MergeTree() +PARTITION BY tuple() +ORDER BY tuple() + +INSERT INTO whitespaces SELECT value +FROM +( + SELECT + arrayStringConcat(groupArray(' ')) AS spaces, + concat(spaces, toString(any(number)), spaces) AS value + FROM numbers(100000000) + GROUP BY pow(number, intHash32(number) % 4) % 12345678 +) -- repeat something like this multiple times and/or just copy whitespaces table into itself diff --git a/dbms/tests/queries/0_stateless/00514_interval_operators.reference b/dbms/tests/queries/0_stateless/00514_interval_operators.reference index 8af8f56eb87..43238eecb3d 100644 --- a/dbms/tests/queries/0_stateless/00514_interval_operators.reference +++ b/dbms/tests/queries/0_stateless/00514_interval_operators.reference @@ -36,3 +36,4 @@ 2029-02-28 01:02:03 2017-03-29 01:02:03 2030-02-28 01:02:03 2017-04-29 01:02:03 2031-02-28 01:02:03 2017-05-29 01:02:03 +2015-11-29 01:02:03 diff --git a/dbms/tests/queries/0_stateless/00514_interval_operators.sql b/dbms/tests/queries/0_stateless/00514_interval_operators.sql index 9dc2f67322b..a4b6c983abf 100644 --- a/dbms/tests/queries/0_stateless/00514_interval_operators.sql +++ b/dbms/tests/queries/0_stateless/00514_interval_operators.sql @@ -2,3 +2,4 @@ SELECT toDateTime('2017-10-30 08:18:19') + INTERVAL 1 DAY + INTERVAL 1 MONTH - I SELECT toDateTime('2017-10-30 08:18:19') + INTERVAL 1 HOUR + INTERVAL 1000 MINUTE + INTERVAL 10 SECOND; SELECT toDateTime('2017-10-30 08:18:19') + INTERVAL 1 DAY + INTERVAL number MONTH FROM system.numbers LIMIT 20; SELECT toDateTime('2016-02-29 01:02:03') + INTERVAL number YEAR, toDateTime('2016-02-29 01:02:03') + INTERVAL number MONTH FROM system.numbers LIMIT 16; +SELECT toDateTime('2016-02-29 01:02:03') - INTERVAL 1 QUARTER; diff --git a/dbms/tests/queries/0_stateless/00619_extract.sql b/dbms/tests/queries/0_stateless/00619_extract.sql index 78ec812dad6..034ae55b5e3 100644 --- a/dbms/tests/queries/0_stateless/00619_extract.sql +++ b/dbms/tests/queries/0_stateless/00619_extract.sql @@ -13,7 +13,7 @@ SELECT EXTRACT(year FROM toDateTime('2017-12-31 18:59:58')); DROP TABLE IF EXISTS test.Orders; CREATE TABLE test.Orders (OrderId UInt64, OrderName String, OrderDate DateTime) engine = Log; insert into test.Orders values (1, 'Jarlsberg Cheese', toDateTime('2008-10-11 13:23:44')); -SELECT EXTRACT(YEAR FROM OrderDate) AS OrderYear, EXTRACT(MONTH FROM OrderDate) AS OrderMonth, EXTRACT(DAY FROM OrderDate) AS OrderDay, +SELECT EXTRACT(YYYY FROM OrderDate) AS OrderYear, EXTRACT(MONTH FROM OrderDate) AS OrderMonth, EXTRACT(DAY FROM OrderDate) AS OrderDay, EXTRACT(HOUR FROM OrderDate), EXTRACT(MINUTE FROM OrderDate), EXTRACT(SECOND FROM OrderDate) FROM test.Orders WHERE OrderId=1; DROP TABLE test.Orders; diff --git a/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.reference b/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.reference index 7a70e443c1b..6a2a0523476 100644 --- a/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.reference +++ b/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.reference @@ -10,3 +10,19 @@ o 1 oo o +fo +foo +r +bar + +foo + foo +xxfoo +fooabba +fooabbafoo +foo* +-11 +-3 +2021-01-01 +2018-07-18 01:02:03 +2018-04-01 diff --git a/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.sql b/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.sql index 248514d134b..a7f1f3ad98a 100644 --- a/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.sql +++ b/dbms/tests/queries/0_stateless/00765_sql_compatibility_aliases.sql @@ -12,3 +12,19 @@ select mid('foo', 3); select IF(3>2, 1, 0); select substring('foo' from 1 + 1); select SUBSTRING('foo' FROM 2 FOR 1); +select left('foo', 2); +select LEFT('foo', 123); +select RIGHT('bar', 1); +select right('bar', 123); +select ltrim('') || rtrim('') || trim(''); +select ltrim(' foo'); +select RTRIM(' foo '); +select trim(TRAILING 'x' FROM 'xxfooxx'); +select Trim(LEADING 'ab' FROM 'abbafooabba'); +select TRIM(both 'ab' FROM 'abbafooabbafooabba'); +select trim(LEADING '*[]{}|\\' FROM '\\|[[[}}}*foo*'); +select DATE_DIFF(MONTH, toDate('2018-12-18'), toDate('2018-01-01')); +select DATE_DIFF(QQ, toDate('2018-12-18'), toDate('2018-01-01')); +select DATE_ADD(YEAR, 3, toDate('2018-01-01')); +select timestamp_sub(SQL_TSI_MONTH, 5, toDateTime('2018-12-18 01:02:03')); +select timestamp_ADD(toDate('2018-01-01'), INTERVAL 3 MONTH); diff --git a/libs/libcommon/include/common/DateLUTImpl.h b/libs/libcommon/include/common/DateLUTImpl.h index 56d9cc04dd1..55a94f3733a 100644 --- a/libs/libcommon/include/common/DateLUTImpl.h +++ b/libs/libcommon/include/common/DateLUTImpl.h @@ -584,6 +584,16 @@ public: } } + inline time_t addQuarters(time_t t, Int64 delta) const + { + return addMonths(t, delta * 3); + } + + inline DayNum addQuarters(DayNum d, Int64 delta) const + { + return addMonths(d, delta * 3); + } + /// Saturation can occur if 29 Feb is mapped to non-leap year. inline time_t addYears(time_t t, Int64 delta) const {