diff --git a/velox/docs/functions/presto/datetime.rst b/velox/docs/functions/presto/datetime.rst index 8e9ceaade0af..526ea7c2bb35 100644 --- a/velox/docs/functions/presto/datetime.rst +++ b/velox/docs/functions/presto/datetime.rst @@ -15,9 +15,15 @@ Date and Time Operators * - ``+`` - ``interval '1' second + interval '1' hour`` - ``0 01:00:01.000`` + * - ``+`` + - ``timestamp '1970-01-01 00:00:00.000' + interval '1' second`` + - ``1970-01-01 00:00:01.000`` * - ``-`` - ``interval '1' hour - interval '1' second`` - ``0 00:59:59.000`` + * - ``-`` + - ``timestamp '1970-01-01 00:00:00.000' - interval '1' second`` + - ``1969-12-31 23:59:59.000`` * - ``*`` - ``interval '1' second * 2`` - ``0 00:00:02.000`` @@ -36,17 +42,21 @@ Date and Time Operators .. function:: plus(x, y) -> [same as x] - Returns the sum of ``x`` and ``y``. ``x`` and ``y`` are both intervals day - to second. Returns ``-106751991167 07:12:55.808`` when the addition - overflows in positive. Returns ``106751991167 07:12:55.807`` when the - addition overflows in negative. + Returns the sum of ``x`` and ``y``. Both ``x`` and ``y`` are intervals day + to second or one of them can be timestamp. For addition of two intervals day to + second, returns ``-106751991167 07:12:55.808`` when the addition overflows + in positive and returns ``106751991167 07:12:55.807`` when the addition + overflows in negative. When addition of a timestamp with an interval day to + second, overflowed results are wrapped around. .. function:: minus(x, y) -> [same as x] - Returns the result of subtracting ``y`` from ``x``. ``x`` and ``y`` are - both intervals day to second. Returns ``-106751991167 07:12:55.808`` when - the subtraction overflows in positive. Returns ``106751991167 07:12:55.807`` - when the subtraction overflows in negative. + Returns the result of subtracting ``y`` from ``x``. Both ``x`` and ``y`` + are intervals day to second or ``x`` can be timestamp. For subtraction of + two intervals day to second, returns ``-106751991167 07:12:55.808`` when + the subtraction overflows in positive and returns ``106751991167 07:12:55.807`` + when the subtraction overflows in negative. For subtraction of an interval + day to second from a timestamp, overflowed results are wrapped around. .. function:: multiply(interval day to second, x) -> interval day to second diff --git a/velox/functions/prestosql/DateTimeFunctions.h b/velox/functions/prestosql/DateTimeFunctions.h index 89bc219e5db2..d248fe3e0d1d 100644 --- a/velox/functions/prestosql/DateTimeFunctions.h +++ b/velox/functions/prestosql/DateTimeFunctions.h @@ -401,7 +401,7 @@ struct DatePlusIntervalDayTime { }; template -struct TimestampMinusIntervalDayTime { +struct TimestampMinusFunction { VELOX_DEFINE_FUNCTION_TYPES(T); FOLLY_ALWAYS_INLINE void call( @@ -412,6 +412,60 @@ struct TimestampMinusIntervalDayTime { } }; +template +struct TimestampPlusIntervalDayTime { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE void call( + out_type& result, + const arg_type& a, + const arg_type& b) +#if defined(__has_feature) +#if __has_feature(__address_sanitizer__) + __attribute__((__no_sanitize__("signed-integer-overflow"))) +#endif +#endif + { + result = Timestamp::fromMillisNoError(a.toMillis() + b); + } +}; + +template +struct IntervalDayTimePlusTimestamp { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE void call( + out_type& result, + const arg_type& a, + const arg_type& b) +#if defined(__has_feature) +#if __has_feature(__address_sanitizer__) + __attribute__((__no_sanitize__("signed-integer-overflow"))) +#endif +#endif + { + result = Timestamp::fromMillisNoError(a + b.toMillis()); + } +}; + +template +struct TimestampMinusIntervalDayTime { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE void call( + out_type& result, + const arg_type& a, + const arg_type& b) +#if defined(__has_feature) +#if __has_feature(__address_sanitizer__) + __attribute__((__no_sanitize__("signed-integer-overflow"))) +#endif +#endif + { + result = Timestamp::fromMillisNoError(a.toMillis() - b); + } +}; + template struct DayOfWeekFunction : public InitSessionTimezone, public TimestampWithTimezoneSupport { diff --git a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp index c6dcfecb71d7..2594d2bf8d49 100644 --- a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp +++ b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp @@ -65,6 +65,21 @@ void registerSimpleFunctions(const std::string& prefix) { {prefix + "plus"}); registerFunction< TimestampMinusIntervalDayTime, + Timestamp, + Timestamp, + IntervalDayTime>({prefix + "minus"}); + registerFunction< + TimestampPlusIntervalDayTime, + Timestamp, + Timestamp, + IntervalDayTime>({prefix + "plus"}); + registerFunction< + IntervalDayTimePlusTimestamp, + Timestamp, + IntervalDayTime, + Timestamp>({prefix + "plus"}); + registerFunction< + TimestampMinusFunction, IntervalDayTime, Timestamp, Timestamp>({prefix + "minus"}); diff --git a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp index e13eb56ebf5e..af05d1c9cfb7 100644 --- a/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp +++ b/velox/functions/prestosql/tests/DateTimeFunctionsTest.cpp @@ -837,7 +837,84 @@ TEST_F(DateTimeFunctionsTest, plusMinusDateIntervalDayTime) { EXPECT_THROW(minus(baseDate, partDay), VeloxUserError); } -TEST_F(DateTimeFunctionsTest, minusTimestampIntervalDayTime) { +TEST_F(DateTimeFunctionsTest, plusMinusTimestampIntervalDayTime) { + constexpr int64_t kLongMax = std::numeric_limits::max(); + constexpr int64_t kLongMin = std::numeric_limits::min(); + + const auto minus = [&](std::optional timestamp, + std::optional interval) { + return evaluateOnce( + "c0 - c1", + makeRowVector({ + makeNullableFlatVector({timestamp}), + makeNullableFlatVector({interval}, INTERVAL_DAY_TIME()), + })); + }; + + EXPECT_EQ(std::nullopt, minus(std::nullopt, std::nullopt)); + EXPECT_EQ(std::nullopt, minus(std::nullopt, 1)); + EXPECT_EQ(std::nullopt, minus(Timestamp(0, 0), std::nullopt)); + EXPECT_EQ(Timestamp(0, 0), minus(Timestamp(0, 0), 0)); + EXPECT_EQ(Timestamp(0, 0), minus(Timestamp(10, 0), 10'000)); + EXPECT_EQ(Timestamp(-10, 0), minus(Timestamp(10, 0), 20'000)); + EXPECT_EQ( + Timestamp(-2, 50 * Timestamp::kNanosecondsInMillisecond), + minus(Timestamp(0, 50 * Timestamp::kNanosecondsInMillisecond), 2'000)); + EXPECT_EQ( + Timestamp(-3, 995 * Timestamp::kNanosecondsInMillisecond), + minus(Timestamp(0, 0), 2'005)); + EXPECT_EQ( + Timestamp(9223372036854774, 809000000), + minus(Timestamp(-1, 0), kLongMax)); + EXPECT_EQ( + Timestamp(-9223372036854775, 192000000), + minus(Timestamp(1, 0), kLongMin)); + + const auto plusAndVerify = [&](std::optional timestamp, + std::optional interval, + std::optional expected) { + EXPECT_EQ( + expected, + evaluateOnce( + "c0 + c1", + makeRowVector({ + makeNullableFlatVector({timestamp}), + makeNullableFlatVector( + {interval}, INTERVAL_DAY_TIME()), + }))); + EXPECT_EQ( + expected, + evaluateOnce( + "c1 + c0", + makeRowVector({ + makeNullableFlatVector({timestamp}), + makeNullableFlatVector( + {interval}, INTERVAL_DAY_TIME()), + }))); + }; + + plusAndVerify(std::nullopt, std::nullopt, std::nullopt); + plusAndVerify(std::nullopt, 1, std::nullopt); + plusAndVerify(Timestamp(0, 0), std::nullopt, std::nullopt); + plusAndVerify(Timestamp(0, 0), 0, Timestamp(0, 0)); + plusAndVerify(Timestamp(0, 0), 10'000, Timestamp(10, 0)); + plusAndVerify( + Timestamp(0, 0), + 20'005, + Timestamp(20, 5 * Timestamp::kNanosecondsInMillisecond)); + plusAndVerify( + Timestamp(0, 0), + -30'005, + Timestamp(-31, 995 * Timestamp::kNanosecondsInMillisecond)); + plusAndVerify( + Timestamp(1, 0), kLongMax, Timestamp(-9223372036854775, 191000000)); + plusAndVerify( + Timestamp(0, 0), kLongMin, Timestamp(-9223372036854776, 192000000)); + plusAndVerify( + Timestamp(-1, 0), kLongMin, Timestamp(9223372036854774, 808000000)); +} + +TEST_F(DateTimeFunctionsTest, minusTimestamp) { const auto minus = [&](std::optional t1, std::optional t2) { const auto timestamp1 = (t1.has_value()) ? Timestamp(t1.value(), 0) : std::optional(); diff --git a/velox/type/Timestamp.h b/velox/type/Timestamp.h index 46cb32a2a5f3..ea3a99cd77cd 100644 --- a/velox/type/Timestamp.h +++ b/velox/type/Timestamp.h @@ -167,6 +167,21 @@ struct Timestamp { return Timestamp(second, nano); } + static Timestamp fromMillisNoError(int64_t millis) +#if defined(__has_feature) +#if __has_feature(__address_sanitizer__) + __attribute__((__no_sanitize__("signed-integer-overflow"))) +#endif +#endif + { + if (millis >= 0 || millis % 1'000 == 0) { + return Timestamp(millis / 1'000, (millis % 1'000) * 1'000'000); + } + auto second = millis / 1'000 - 1; + auto nano = ((millis - second * 1'000) % 1'000) * 1'000'000; + return Timestamp(second, nano); + } + static Timestamp fromMicros(int64_t micros) { if (micros >= 0 || micros % 1'000'000 == 0) { return Timestamp(micros / 1'000'000, (micros % 1'000'000) * 1'000);