From dcd79e7597d3f34a3206edc045fa763d27fa0e78 Mon Sep 17 00:00:00 2001 From: jiao_lv Date: Mon, 28 Nov 2022 13:49:32 +0800 Subject: [PATCH] Implement datetime functions in velox/sparksql. (#81) --- .../DateTimeFunctionsRegistration.cpp | 3 - velox/functions/sparksql/DateTime.h | 435 ++++++++ velox/functions/sparksql/Register.cpp | 55 +- velox/functions/sparksql/tests/CMakeLists.txt | 1 + .../functions/sparksql/tests/DateTimeTest.cpp | 955 ++++++++++++++++++ velox/substrait/SubstraitToVeloxExpr.cpp | 9 +- velox/substrait/SubstraitToVeloxExpr.h | 15 + 7 files changed, 1463 insertions(+), 10 deletions(-) create mode 100644 velox/functions/sparksql/DateTime.h create mode 100644 velox/functions/sparksql/tests/DateTimeTest.cpp diff --git a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp index 11e776000461..c79661f8e21f 100644 --- a/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp +++ b/velox/functions/prestosql/registration/DateTimeFunctionsRegistration.cpp @@ -34,9 +34,6 @@ void registerSimpleFunctions() { registerFunction({"week", "week_of_year"}); registerFunction( {"week", "week_of_year"}); - // registerFunction({"year"}); - // registerFunction({"year"}); - // registerFunction({"year"}); registerFunction({"quarter"}); registerFunction({"quarter"}); registerFunction( diff --git a/velox/functions/sparksql/DateTime.h b/velox/functions/sparksql/DateTime.h new file mode 100644 index 000000000000..dd4d8c3891e2 --- /dev/null +++ b/velox/functions/sparksql/DateTime.h @@ -0,0 +1,435 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "velox/core/QueryConfig.h" +#include "velox/external/date/tz.h" +#include "velox/functions/Macros.h" +#include "velox/functions/lib/DateTimeFormatter.h" +#include "velox/functions/lib/JodaDateTime.h" +#include "velox/functions/prestosql/DateTimeImpl.h" +#include "velox/functions/prestosql/types/TimestampWithTimeZoneType.h" +#include "velox/type/Type.h" +#include "velox/type/tz/TimeZoneMap.h" + +namespace facebook::velox::functions { + +template +struct ToUnixtimeFunction { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE bool call( + double& result, + const arg_type& timestamp) { + result = toUnixtime(timestamp); + return true; + } + + FOLLY_ALWAYS_INLINE bool call( + double& result, + const arg_type& timestampWithTimezone) { + const auto milliseconds = *timestampWithTimezone.template at<0>(); + result = (double)milliseconds / kMillisecondsInSecond; + return true; + } +}; + +template +struct FromUnixtimeFunction { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE bool call( + Timestamp& result, + const arg_type& unixtime) { + auto resultOptional = fromUnixtime(unixtime); + if (LIKELY(resultOptional.has_value())) { + result = resultOptional.value(); + return true; + } + return false; + } +}; + +namespace { +inline constexpr int64_t kSecondsInDay = 86'400; + +FOLLY_ALWAYS_INLINE const date::time_zone* getTimeZoneFromConfig( + const core::QueryConfig& config) { + if (config.adjustTimestampToTimezone()) { + auto sessionTzName = config.sessionTimezone(); + if (!sessionTzName.empty()) { + return date::locate_zone(sessionTzName); + } + } + return nullptr; +} + +FOLLY_ALWAYS_INLINE int64_t +getSeconds(Timestamp timestamp, const date::time_zone* timeZone) { + if (timeZone != nullptr) { + timestamp.toTimezone(*timeZone); + return timestamp.getSeconds(); + } else { + return timestamp.getSeconds(); + } +} + +FOLLY_ALWAYS_INLINE +std::tm getDateTime(Timestamp timestamp, const date::time_zone* timeZone) { + int64_t seconds = getSeconds(timestamp, timeZone); + std::tm dateTime; + gmtime_r((const time_t*)&seconds, &dateTime); + return dateTime; +} + +FOLLY_ALWAYS_INLINE +std::tm getDateTime(Date date) { + int64_t seconds = date.days() * kSecondsInDay; + std::tm dateTime; + gmtime_r((const time_t*)&seconds, &dateTime); + return dateTime; +} + +template +struct InitSessionTimezone { + VELOX_DEFINE_FUNCTION_TYPES(T); + const date::time_zone* timeZone_{nullptr}; + + FOLLY_ALWAYS_INLINE void initialize( + const core::QueryConfig& config, + const arg_type* /*timestamp*/) { + timeZone_ = getTimeZoneFromConfig(config); + } +}; + +template +struct TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + // Convert timestampWithTimezone to a timestamp representing the moment at the + // zone in timestampWithTimezone. + FOLLY_ALWAYS_INLINE + Timestamp toTimestamp( + const arg_type& timestampWithTimezone) { + const auto milliseconds = *timestampWithTimezone.template at<0>(); + Timestamp timestamp = Timestamp::fromMillis(milliseconds); + timestamp.toTimezone(*timestampWithTimezone.template at<1>()); + + return timestamp; + } +}; + +} // namespace + +template +struct QuarterFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE int64_t getQuarter(const std::tm& time) { + return time.tm_mon / 3 + 1; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getQuarter(getDateTime(timestamp, this->timeZone_)); + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getQuarter(getDateTime(date)); + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getQuarter(getDateTime(timestamp, nullptr)); + } +}; + +template +struct MonthFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE int64_t getMonth(const std::tm& time) { + return 1 + time.tm_mon; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getMonth(getDateTime(timestamp, this->timeZone_)); + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getMonth(getDateTime(date)); + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getMonth(getDateTime(timestamp, nullptr)); + } +}; + +template +struct DayFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDateTime(timestamp, this->timeZone_).tm_mday; + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDateTime(date).tm_mday; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDateTime(timestamp, nullptr).tm_mday; + } +}; + +template +struct DayOfWeekFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE int64_t getDayOfWeek(const std::tm& time) { + return time.tm_wday + 1 == 0 ? 7 : time.tm_wday + 1; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDayOfWeek(getDateTime(timestamp, this->timeZone_)); + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDayOfWeek(getDateTime(date)); + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDayOfWeek(getDateTime(timestamp, nullptr)); + } +}; + +template +struct DayOfYearFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE int64_t getDayOfYear(const std::tm& time) { + return time.tm_yday + 1; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDayOfYear(getDateTime(timestamp, this->timeZone_)); + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDayOfYear(getDateTime(date)); + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDayOfYear(getDateTime(timestamp, nullptr)); + } +}; + +template +struct YearOfWeekFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + FOLLY_ALWAYS_INLINE int64_t computeYearOfWeek(const std::tm& dateTime) { + int isoWeekDay = dateTime.tm_wday == 0 ? 7 : dateTime.tm_wday; + // The last few days in December may belong to the next year if they are + // in the same week as the next January 1 and this January 1 is a Thursday + // or before. + if (UNLIKELY( + dateTime.tm_mon == 11 && dateTime.tm_mday >= 29 && + dateTime.tm_mday - isoWeekDay >= 31 - 3)) { + return 1900 + dateTime.tm_year + 1; + } + // The first few days in January may belong to the last year if they are + // in the same week as January 1 and January 1 is a Friday or after. + else if (UNLIKELY( + dateTime.tm_mon == 0 && dateTime.tm_mday <= 3 && + isoWeekDay - (dateTime.tm_mday - 1) >= 5)) { + return 1900 + dateTime.tm_year - 1; + } else { + return 1900 + dateTime.tm_year; + } + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = computeYearOfWeek(getDateTime(timestamp, this->timeZone_)); + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = computeYearOfWeek(getDateTime(date)); + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = computeYearOfWeek(getDateTime(timestamp, nullptr)); + } +}; + +template +struct HourFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDateTime(timestamp, this->timeZone_).tm_hour; + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDateTime(date).tm_hour; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDateTime(timestamp, nullptr).tm_hour; + } +}; + +template +struct MinuteFunction : public InitSessionTimezone, + public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDateTime(timestamp, this->timeZone_).tm_min; + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDateTime(date).tm_min; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDateTime(timestamp, nullptr).tm_min; + } +}; + +template +struct SecondFunction : public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = getDateTime(timestamp, nullptr).tm_sec; + } + + template + FOLLY_ALWAYS_INLINE void call(TInput& result, const arg_type& date) { + result = getDateTime(date).tm_sec; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = getDateTime(timestamp, nullptr).tm_sec; + } +}; + +template +struct MillisecondFunction : public TimestampWithTimezoneSupport { + VELOX_DEFINE_FUNCTION_TYPES(T); + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestamp) { + result = timestamp.getNanos() / kNanosecondsInMillisecond; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& /*date*/) { + // Dates do not have millisecond granularity. + result = 0; + } + + template + FOLLY_ALWAYS_INLINE void call( + TInput& result, + const arg_type& timestampWithTimezone) { + auto timestamp = this->toTimestamp(timestampWithTimezone); + result = timestamp.getNanos() / kNanosecondsInMillisecond; + } +}; + +} // namespace facebook::velox::functions diff --git a/velox/functions/sparksql/Register.cpp b/velox/functions/sparksql/Register.cpp index 575d1d6b21f4..3ffed4d6d01f 100644 --- a/velox/functions/sparksql/Register.cpp +++ b/velox/functions/sparksql/Register.cpp @@ -24,6 +24,7 @@ #include "velox/functions/sparksql/ArraySort.h" #include "velox/functions/sparksql/Bitwise.h" #include "velox/functions/sparksql/CompareFunctionsNullSafe.h" +#include "velox/functions/sparksql/DateTime.h" #include "velox/functions/sparksql/DateTimeFunctions.h" #include "velox/functions/sparksql/Hash.h" #include "velox/functions/sparksql/In.h" @@ -37,9 +38,8 @@ namespace facebook::velox::functions { static void workAroundRegistrationMacro(const std::string& prefix) { - // VELOX_REGISTER_VECTOR_FUNCTION must be invoked in the same namespace as the - // vector function definition. - // Higher order functions. + // VELOX_REGISTER_VECTOR_FUNCTION must be invoked in the same namespace as + // the vector function definition. Higher order functions. VELOX_REGISTER_VECTOR_FUNCTION(udf_transform, prefix + "transform"); VELOX_REGISTER_VECTOR_FUNCTION(udf_reduce, prefix + "aggregate"); VELOX_REGISTER_VECTOR_FUNCTION(udf_array_filter, prefix + "filter"); @@ -153,6 +153,55 @@ void registerFunctions(const std::string& prefix) { registerFunction({"year"}); registerFunction({"year"}); + // Register DateTime functions. + registerFunction( + {prefix + "millisecond"}); + registerFunction( + {prefix + "millisecond"}); + registerFunction( + {prefix + "millisecond"}); + registerFunction({prefix + "second"}); + registerFunction({prefix + "second"}); + registerFunction( + {prefix + "second"}); + registerFunction({prefix + "minute"}); + registerFunction({prefix + "minute"}); + registerFunction( + {prefix + "minute"}); + registerFunction({prefix + "hour"}); + registerFunction({prefix + "hour"}); + registerFunction( + {prefix + "hour"}); + registerFunction( + {prefix + "day", prefix + "day_of_month"}); + registerFunction( + {prefix + "day", prefix + "day_of_month"}); + registerFunction( + {prefix + "day", prefix + "day_of_month"}); + registerFunction({prefix + "day_of_week"}); + registerFunction( + {prefix + "day_of_week"}); + registerFunction( + {prefix + "day_of_week"}); + registerFunction({prefix + "day_of_year"}); + registerFunction( + {prefix + "day_of_year"}); + registerFunction( + {prefix + "day_of_year"}); + registerFunction({prefix + "month"}); + registerFunction({prefix + "month"}); + registerFunction( + {prefix + "month"}); + registerFunction({prefix + "quarter"}); + registerFunction({prefix + "quarter"}); + registerFunction( + {prefix + "quarter"}); + registerFunction( + {prefix + "year_of_week"}); + registerFunction( + {prefix + "year_of_week"}); + registerFunction( + {prefix + "year_of_week"}); } } // namespace sparksql diff --git a/velox/functions/sparksql/tests/CMakeLists.txt b/velox/functions/sparksql/tests/CMakeLists.txt index fcba2feede23..54a4bda6361f 100644 --- a/velox/functions/sparksql/tests/CMakeLists.txt +++ b/velox/functions/sparksql/tests/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable( BitwiseTest.cpp CompareNullSafeTests.cpp DateTimeFunctionsTest.cpp + DateTimeTest.cpp HashTest.cpp InTest.cpp LeastGreatestTest.cpp diff --git a/velox/functions/sparksql/tests/DateTimeTest.cpp b/velox/functions/sparksql/tests/DateTimeTest.cpp new file mode 100644 index 000000000000..a0269de94efc --- /dev/null +++ b/velox/functions/sparksql/tests/DateTimeTest.cpp @@ -0,0 +1,955 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "velox/functions/sparksql/tests/SparkFunctionBaseTest.h" +#include "velox/functions/prestosql/types/TimestampWithTimeZoneType.h" +#include "velox/type/Date.h" +#include "velox/type/Timestamp.h" +#include "velox/type/TimestampConversion.h" +#include "velox/type/tz/TimeZoneMap.h" + +using namespace facebook::velox::test; + +namespace facebook::velox::functions::sparksql::test { +namespace { + +class DateTimeTest : public SparkFunctionBaseTest { + protected: + std::string daysShort[7] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; + + std::string daysLong[7] = { + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"}; + + std::string monthsShort[12] = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"}; + + std::string monthsLong[12] = { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"}; + + std::string padNumber(int number) { + return number < 10 ? "0" + std::to_string(number) : std::to_string(number); + } + + void setQueryTimeZone(const std::string& timeZone) { + queryCtx_->setConfigOverridesUnsafe({ + {core::QueryConfig::kSessionTimezone, timeZone}, + {core::QueryConfig::kAdjustTimestampToTimezone, "true"}, + }); + } + + void disableAdjustTimestampToTimezone() { + queryCtx_->setConfigOverridesUnsafe({ + {core::QueryConfig::kAdjustTimestampToTimezone, "false"}, + }); + } + + public: + struct TimestampWithTimezone { + TimestampWithTimezone(int64_t milliSeconds, int16_t timezoneId) + : milliSeconds_(milliSeconds), timezoneId_(timezoneId) {} + + int64_t milliSeconds_{0}; + int16_t timezoneId_{0}; + }; + + std::optional parseDatetime( + const std::optional& input, + const std::optional& format) { + auto resultVector = evaluate( + "parse_datetime(c0, c1)", + makeRowVector( + {makeNullableFlatVector({input}), + makeNullableFlatVector({format})})); + EXPECT_EQ(1, resultVector->size()); + + if (resultVector->isNullAt(0)) { + return std::nullopt; + } + + auto rowVector = resultVector->as(); + return TimestampWithTimezone{ + rowVector->children()[0]->as>()->valueAt(0), + rowVector->children()[1]->as>()->valueAt(0)}; + } + + std::optional dateParse( + const std::optional& input, + const std::optional& format) { + auto resultVector = evaluate( + "date_parse(c0, c1)", + makeRowVector( + {makeNullableFlatVector({input}), + makeNullableFlatVector({format})})); + EXPECT_EQ(1, resultVector->size()); + + if (resultVector->isNullAt(0)) { + return std::nullopt; + } + return resultVector->as>()->valueAt(0); + } + + std::optional dateFormat( + std::optional timestamp, + const std::string& format) { + auto resultVector = evaluate( + "date_format(c0, c1)", + makeRowVector( + {makeNullableFlatVector({timestamp}), + makeNullableFlatVector({format})})); + return resultVector->as>()->valueAt(0); + } + + std::optional formatDatetime( + std::optional timestamp, + const std::string& format) { + auto resultVector = evaluate( + "format_datetime(c0, c1)", + makeRowVector( + {makeNullableFlatVector({timestamp}), + makeNullableFlatVector({format})})); + return resultVector->as>()->valueAt(0); + } + + template + std::optional evaluateWithTimestampWithTimezone( + const std::string& expression, + std::optional timestamp, + const std::optional& timeZoneName) { + if (!timestamp.has_value() || !timeZoneName.has_value()) { + return evaluateOnce( + expression, + makeRowVector({makeRowVector( + { + makeNullableFlatVector({std::nullopt}), + makeNullableFlatVector({std::nullopt}), + }, + [](vector_size_t /*row*/) { return true; })})); + } + + const std::optional tzid = + util::getTimeZoneID(timeZoneName.value()); + return evaluateOnce( + expression, + makeRowVector({makeRowVector({ + makeNullableFlatVector({timestamp}), + makeNullableFlatVector({tzid}), + })})); + } + + VectorPtr evaluateWithTimestampWithTimezone( + const std::string& expression, + std::optional timestamp, + const std::optional& timeZoneName) { + if (!timestamp.has_value() || !timeZoneName.has_value()) { + return evaluate( + expression, + makeRowVector({makeRowVector( + { + makeNullableFlatVector({std::nullopt}), + makeNullableFlatVector({std::nullopt}), + }, + [](vector_size_t /*row*/) { return true; })})); + } + + const std::optional tzid = + util::getTimeZoneID(timeZoneName.value()); + return evaluate( + expression, + makeRowVector({makeRowVector({ + makeNullableFlatVector({timestamp}), + makeNullableFlatVector({tzid}), + })})); + } +}; + +bool operator==( + const DateTimeTest::TimestampWithTimezone& a, + const DateTimeTest::TimestampWithTimezone& b) { + return a.milliSeconds_ == b.milliSeconds_ && a.timezoneId_ == b.timezoneId_; +} + +TEST_F(DateTimeTest, year) { + const auto year = [&](std::optional date) { + return evaluateOnce("year(c0)", date); + }; + EXPECT_EQ(std::nullopt, year(std::nullopt)); + EXPECT_EQ(1970, year(Timestamp(0, 0))); + EXPECT_EQ(1969, year(Timestamp(-1, 9000))); + EXPECT_EQ(2096, year(Timestamp(4000000000, 0))); + EXPECT_EQ(2096, year(Timestamp(4000000000, 123000000))); + EXPECT_EQ(2001, year(Timestamp(998474645, 321000000))); + EXPECT_EQ(2001, year(Timestamp(998423705, 321000000))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, year(std::nullopt)); + EXPECT_EQ(1969, year(Timestamp(0, 0))); + EXPECT_EQ(1969, year(Timestamp(-1, 12300000000))); + EXPECT_EQ(2096, year(Timestamp(4000000000, 0))); + EXPECT_EQ(2096, year(Timestamp(4000000000, 123000000))); + EXPECT_EQ(2001, year(Timestamp(998474645, 321000000))); + EXPECT_EQ(2001, year(Timestamp(998423705, 321000000))); +} + +TEST_F(DateTimeTest, yearDate) { + const auto year = [&](std::optional date) { + return evaluateOnce("year(c0)", date); + }; + EXPECT_EQ(std::nullopt, year(std::nullopt)); + EXPECT_EQ(1970, year(Date(0))); + EXPECT_EQ(1969, year(Date(-1))); + EXPECT_EQ(2020, year(Date(18262))); + EXPECT_EQ(1920, year(Date(-18262))); +} + +TEST_F(DateTimeTest, yearTimestampWithTimezone) { + EXPECT_EQ( + 1969, + evaluateWithTimestampWithTimezone("year(c0)", 0, "-01:00")); + EXPECT_EQ( + 1970, + evaluateWithTimestampWithTimezone("year(c0)", 0, "+00:00")); + EXPECT_EQ( + 1973, + evaluateWithTimestampWithTimezone( + "year(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 1966, + evaluateWithTimestampWithTimezone( + "year(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 2001, + evaluateWithTimestampWithTimezone( + "year(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 1938, + evaluateWithTimestampWithTimezone( + "year(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "year(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, quarter) { + const auto quarter = [&](std::optional date) { + return evaluateOnce("quarter(c0)", date); + }; + EXPECT_EQ(std::nullopt, quarter(std::nullopt)); + EXPECT_EQ(1, quarter(Timestamp(0, 0))); + EXPECT_EQ(4, quarter(Timestamp(-1, 9000))); + EXPECT_EQ(4, quarter(Timestamp(4000000000, 0))); + EXPECT_EQ(4, quarter(Timestamp(4000000000, 123000000))); + EXPECT_EQ(2, quarter(Timestamp(990000000, 321000000))); + EXPECT_EQ(3, quarter(Timestamp(998423705, 321000000))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, quarter(std::nullopt)); + EXPECT_EQ(4, quarter(Timestamp(0, 0))); + EXPECT_EQ(4, quarter(Timestamp(-1, 12300000000))); + EXPECT_EQ(4, quarter(Timestamp(4000000000, 0))); + EXPECT_EQ(4, quarter(Timestamp(4000000000, 123000000))); + EXPECT_EQ(2, quarter(Timestamp(990000000, 321000000))); + EXPECT_EQ(3, quarter(Timestamp(998423705, 321000000))); +} + +TEST_F(DateTimeTest, quarterDate) { + const auto quarter = [&](std::optional date) { + return evaluateOnce("quarter(c0)", date); + }; + EXPECT_EQ(std::nullopt, quarter(std::nullopt)); + EXPECT_EQ(1, quarter(Date(0))); + EXPECT_EQ(4, quarter(Date(-1))); + EXPECT_EQ(4, quarter(Date(-40))); + EXPECT_EQ(2, quarter(Date(110))); + EXPECT_EQ(3, quarter(Date(200))); + EXPECT_EQ(1, quarter(Date(18262))); + EXPECT_EQ(1, quarter(Date(-18262))); +} + +TEST_F(DateTimeTest, quarterTimestampWithTimezone) { + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone("quarter(c0)", 0, "-01:00")); + EXPECT_EQ( + 1, + evaluateWithTimestampWithTimezone("quarter(c0)", 0, "+00:00")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "quarter(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 1, + evaluateWithTimestampWithTimezone( + "quarter(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 2, + evaluateWithTimestampWithTimezone( + "quarter(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 3, + evaluateWithTimestampWithTimezone( + "quarter(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "quarter(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, month) { + const auto month = [&](std::optional date) { + return evaluateOnce("month(c0)", date); + }; + EXPECT_EQ(std::nullopt, month(std::nullopt)); + EXPECT_EQ(1, month(Timestamp(0, 0))); + EXPECT_EQ(12, month(Timestamp(-1, 9000))); + EXPECT_EQ(10, month(Timestamp(4000000000, 0))); + EXPECT_EQ(10, month(Timestamp(4000000000, 123000000))); + EXPECT_EQ(8, month(Timestamp(998474645, 321000000))); + EXPECT_EQ(8, month(Timestamp(998423705, 321000000))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, month(std::nullopt)); + EXPECT_EQ(12, month(Timestamp(0, 0))); + EXPECT_EQ(12, month(Timestamp(-1, 12300000000))); + EXPECT_EQ(10, month(Timestamp(4000000000, 0))); + EXPECT_EQ(10, month(Timestamp(4000000000, 123000000))); + EXPECT_EQ(8, month(Timestamp(998474645, 321000000))); + EXPECT_EQ(8, month(Timestamp(998423705, 321000000))); +} + +TEST_F(DateTimeTest, monthDate) { + const auto month = [&](std::optional date) { + return evaluateOnce("month(c0)", date); + }; + EXPECT_EQ(std::nullopt, month(std::nullopt)); + EXPECT_EQ(1, month(Date(0))); + EXPECT_EQ(12, month(Date(-1))); + EXPECT_EQ(11, month(Date(-40))); + EXPECT_EQ(2, month(Date(40))); + EXPECT_EQ(1, month(Date(18262))); + EXPECT_EQ(1, month(Date(-18262))); +} + +TEST_F(DateTimeTest, monthTimestampWithTimezone) { + EXPECT_EQ( + 12, evaluateWithTimestampWithTimezone("month(c0)", 0, "-01:00")); + EXPECT_EQ( + 1, evaluateWithTimestampWithTimezone("month(c0)", 0, "+00:00")); + EXPECT_EQ( + 11, + evaluateWithTimestampWithTimezone( + "month(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 2, + evaluateWithTimestampWithTimezone( + "month(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "month(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 9, + evaluateWithTimestampWithTimezone( + "month(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "month(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, hour) { + const auto hour = [&](std::optional date) { + return evaluateOnce("hour(c0)", date); + }; + EXPECT_EQ(std::nullopt, hour(std::nullopt)); + EXPECT_EQ(0, hour(Timestamp(0, 0))); + EXPECT_EQ(23, hour(Timestamp(-1, 9000))); + EXPECT_EQ(7, hour(Timestamp(4000000000, 0))); + EXPECT_EQ(7, hour(Timestamp(4000000000, 123000000))); + EXPECT_EQ(10, hour(Timestamp(998474645, 321000000))); + EXPECT_EQ(19, hour(Timestamp(998423705, 321000000))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, hour(std::nullopt)); + EXPECT_EQ(13, hour(Timestamp(0, 0))); + EXPECT_EQ(12, hour(Timestamp(-1, 12300000000))); + // Disabled for now because the TZ for Pacific/Apia in 2096 varies between + // systems. + // EXPECT_EQ(21, hour(Timestamp(4000000000, 0))); + // EXPECT_EQ(21, hour(Timestamp(4000000000, 123000000))); + EXPECT_EQ(23, hour(Timestamp(998474645, 321000000))); + EXPECT_EQ(8, hour(Timestamp(998423705, 321000000))); +} + +TEST_F(DateTimeTest, hourTimestampWithTimezone) { + EXPECT_EQ( + 20, + evaluateWithTimestampWithTimezone( + "hour(c0)", 998423705000, "+01:00")); + EXPECT_EQ( + 12, + evaluateWithTimestampWithTimezone( + "hour(c0)", 41028000, "+01:00")); + EXPECT_EQ( + 13, + evaluateWithTimestampWithTimezone( + "hour(c0)", 41028000, "+02:00")); + EXPECT_EQ( + 14, + evaluateWithTimestampWithTimezone( + "hour(c0)", 41028000, "+03:00")); + EXPECT_EQ( + 8, + evaluateWithTimestampWithTimezone( + "hour(c0)", 41028000, "-03:00")); + EXPECT_EQ( + 1, + evaluateWithTimestampWithTimezone( + "hour(c0)", 41028000, "+14:00")); + EXPECT_EQ( + 9, + evaluateWithTimestampWithTimezone( + "hour(c0)", -100000, "-14:00")); + EXPECT_EQ( + 2, + evaluateWithTimestampWithTimezone( + "hour(c0)", -41028000, "+14:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "hour(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, hourDate) { + const auto hour = [&](std::optional date) { + return evaluateOnce("hour(c0)", date); + }; + EXPECT_EQ(std::nullopt, hour(std::nullopt)); + EXPECT_EQ(0, hour(Date(0))); + EXPECT_EQ(0, hour(Date(-1))); + EXPECT_EQ(0, hour(Date(-40))); + EXPECT_EQ(0, hour(Date(40))); + EXPECT_EQ(0, hour(Date(18262))); + EXPECT_EQ(0, hour(Date(-18262))); +} + +TEST_F(DateTimeTest, dayOfMonth) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_month(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(1, day(Timestamp(0, 0))); + EXPECT_EQ(31, day(Timestamp(-1, 9000))); + EXPECT_EQ(30, day(Timestamp(1632989700, 0))); + EXPECT_EQ(1, day(Timestamp(1633076100, 0))); + EXPECT_EQ(6, day(Timestamp(1633508100, 0))); + EXPECT_EQ(31, day(Timestamp(1635668100, 0))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(31, day(Timestamp(0, 0))); + EXPECT_EQ(31, day(Timestamp(-1, 9000))); + EXPECT_EQ(30, day(Timestamp(1632989700, 0))); + EXPECT_EQ(1, day(Timestamp(1633076100, 0))); + EXPECT_EQ(6, day(Timestamp(1633508100, 0))); + EXPECT_EQ(31, day(Timestamp(1635668100, 0))); +} + +TEST_F(DateTimeTest, dayOfMonthDate) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_month(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(1, day(Date(0))); + EXPECT_EQ(31, day(Date(-1))); + EXPECT_EQ(22, day(Date(-40))); + EXPECT_EQ(10, day(Date(40))); + EXPECT_EQ(1, day(Date(18262))); + EXPECT_EQ(2, day(Date(-18262))); +} + +TEST_F(DateTimeTest, dayOfMonthTimestampWithTimezone) { + EXPECT_EQ( + 31, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", 0, "-01:00")); + EXPECT_EQ( + 1, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", 0, "+00:00")); + EXPECT_EQ( + 30, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 2, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 18, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 14, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "day_of_month(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, dayOfWeek) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_week(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(5, day(Timestamp(0, 0))); + EXPECT_EQ(4, day(Timestamp(-1, 9000))); + EXPECT_EQ(2, day(Timestamp(1633940100, 0))); + EXPECT_EQ(3, day(Timestamp(1634026500, 0))); + EXPECT_EQ(4, day(Timestamp(1634112900, 0))); + EXPECT_EQ(5, day(Timestamp(1634199300, 0))); + EXPECT_EQ(6, day(Timestamp(1634285700, 0))); + EXPECT_EQ(7, day(Timestamp(1634372100, 0))); + EXPECT_EQ(1, day(Timestamp(1633853700, 0))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(4, day(Timestamp(0, 0))); + EXPECT_EQ(4, day(Timestamp(-1, 9000))); + EXPECT_EQ(2, day(Timestamp(1633940100, 0))); + EXPECT_EQ(3, day(Timestamp(1634026500, 0))); + EXPECT_EQ(4, day(Timestamp(1634112900, 0))); + EXPECT_EQ(5, day(Timestamp(1634199300, 0))); + EXPECT_EQ(6, day(Timestamp(1634285700, 0))); + EXPECT_EQ(7, day(Timestamp(1634372100, 0))); + EXPECT_EQ(1, day(Timestamp(1633853700, 0))); +} + +TEST_F(DateTimeTest, dayOfWeekDate) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_week(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(5, day(Date(0))); + EXPECT_EQ(4, day(Date(-1))); + EXPECT_EQ(7, day(Date(-40))); + EXPECT_EQ(3, day(Date(40))); + EXPECT_EQ(4, day(Date(18262))); + EXPECT_EQ(6, day(Date(-18262))); +} + +TEST_F(DateTimeTest, dayOfWeekTimestampWithTimezone) { + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", 0, "-01:00")); + EXPECT_EQ( + 5, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", 0, "+00:00")); + EXPECT_EQ( + 6, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "day_of_week(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, dayOfYear) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_year(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(1, day(Timestamp(0, 0))); + EXPECT_EQ(365, day(Timestamp(-1, 9000))); + EXPECT_EQ(273, day(Timestamp(1632989700, 0))); + EXPECT_EQ(274, day(Timestamp(1633076100, 0))); + EXPECT_EQ(279, day(Timestamp(1633508100, 0))); + EXPECT_EQ(304, day(Timestamp(1635668100, 0))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(365, day(Timestamp(0, 0))); + EXPECT_EQ(365, day(Timestamp(-1, 9000))); + EXPECT_EQ(273, day(Timestamp(1632989700, 0))); + EXPECT_EQ(274, day(Timestamp(1633076100, 0))); + EXPECT_EQ(279, day(Timestamp(1633508100, 0))); + EXPECT_EQ(304, day(Timestamp(1635668100, 0))); +} + +TEST_F(DateTimeTest, dayOfYearDate) { + const auto day = [&](std::optional date) { + return evaluateOnce("day_of_year(c0)", date); + }; + EXPECT_EQ(std::nullopt, day(std::nullopt)); + EXPECT_EQ(1, day(Date(0))); + EXPECT_EQ(365, day(Date(-1))); + EXPECT_EQ(326, day(Date(-40))); + EXPECT_EQ(41, day(Date(40))); + EXPECT_EQ(1, day(Date(18262))); + EXPECT_EQ(2, day(Date(-18262))); +} + +TEST_F(DateTimeTest, dayOfYearTimestampWithTimezone) { + EXPECT_EQ( + 365, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", 0, "-01:00")); + EXPECT_EQ( + 1, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", 0, "+00:00")); + EXPECT_EQ( + 334, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 33, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 108, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 257, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "day_of_year(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, yearOfWeek) { + const auto yow = [&](std::optional date) { + return evaluateOnce("year_of_week(c0)", date); + }; + EXPECT_EQ(std::nullopt, yow(std::nullopt)); + EXPECT_EQ(1970, yow(Timestamp(0, 0))); + EXPECT_EQ(1970, yow(Timestamp(-1, 0))); + EXPECT_EQ(1969, yow(Timestamp(-345600, 0))); + EXPECT_EQ(1970, yow(Timestamp(-259200, 0))); + EXPECT_EQ(1970, yow(Timestamp(31536000, 0))); + EXPECT_EQ(1970, yow(Timestamp(31708800, 0))); + EXPECT_EQ(1971, yow(Timestamp(31795200, 0))); + EXPECT_EQ(2021, yow(Timestamp(1632989700, 0))); + + setQueryTimeZone("Pacific/Apia"); + + EXPECT_EQ(std::nullopt, yow(std::nullopt)); + EXPECT_EQ(1970, yow(Timestamp(0, 0))); + EXPECT_EQ(1970, yow(Timestamp(-1, 0))); + EXPECT_EQ(1969, yow(Timestamp(-345600, 0))); + EXPECT_EQ(1969, yow(Timestamp(-259200, 0))); + EXPECT_EQ(1970, yow(Timestamp(31536000, 0))); + EXPECT_EQ(1970, yow(Timestamp(31708800, 0))); + EXPECT_EQ(1970, yow(Timestamp(31795200, 0))); + EXPECT_EQ(2021, yow(Timestamp(1632989700, 0))); +} + +TEST_F(DateTimeTest, yearOfWeekDate) { + const auto yow = [&](std::optional date) { + return evaluateOnce("year_of_week(c0)", date); + }; + EXPECT_EQ(std::nullopt, yow(std::nullopt)); + EXPECT_EQ(1970, yow(Date(0))); + EXPECT_EQ(1970, yow(Date(-1))); + EXPECT_EQ(1969, yow(Date(-4))); + EXPECT_EQ(1970, yow(Date(-3))); + EXPECT_EQ(1970, yow(Date(365))); + EXPECT_EQ(1970, yow(Date(367))); + EXPECT_EQ(1971, yow(Date(368))); + EXPECT_EQ(2021, yow(Date(18900))); +} + +TEST_F(DateTimeTest, yearOfWeekTimestampWithTimezone) { + EXPECT_EQ( + 1970, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", 0, "-01:00")); + EXPECT_EQ( + 1970, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", 0, "+00:00")); + EXPECT_EQ( + 1973, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", 123456789000, "+14:00")); + EXPECT_EQ( + 1966, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", -123456789000, "+03:00")); + EXPECT_EQ( + 2001, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", 987654321000, "-07:00")); + EXPECT_EQ( + 1938, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", -987654321000, "-13:00")); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "year_of_week(c0)", std::nullopt, std::nullopt)); +} + +TEST_F(DateTimeTest, minute) { + const auto minute = [&](std::optional date) { + return evaluateOnce("minute(c0)", date); + }; + EXPECT_EQ(std::nullopt, minute(std::nullopt)); + EXPECT_EQ(0, minute(Timestamp(0, 0))); + EXPECT_EQ(59, minute(Timestamp(-1, 9000))); + EXPECT_EQ(6, minute(Timestamp(4000000000, 0))); + EXPECT_EQ(6, minute(Timestamp(4000000000, 123000000))); + EXPECT_EQ(4, minute(Timestamp(998474645, 321000000))); + EXPECT_EQ(55, minute(Timestamp(998423705, 321000000))); + + setQueryTimeZone("Asia/Kolkata"); + + EXPECT_EQ(std::nullopt, minute(std::nullopt)); + EXPECT_EQ(30, minute(Timestamp(0, 0))); + EXPECT_EQ(29, minute(Timestamp(-1, 9000))); + EXPECT_EQ(36, minute(Timestamp(4000000000, 0))); + EXPECT_EQ(36, minute(Timestamp(4000000000, 123000000))); + EXPECT_EQ(34, minute(Timestamp(998474645, 321000000))); + EXPECT_EQ(25, minute(Timestamp(998423705, 321000000))); +} + +TEST_F(DateTimeTest, minuteDate) { + const auto minute = [&](std::optional date) { + return evaluateOnce("minute(c0)", date); + }; + EXPECT_EQ(std::nullopt, minute(std::nullopt)); + EXPECT_EQ(0, minute(Date(0))); + EXPECT_EQ(0, minute(Date(-1))); + EXPECT_EQ(0, minute(Date(-40))); + EXPECT_EQ(0, minute(Date(40))); + EXPECT_EQ(0, minute(Date(18262))); + EXPECT_EQ(0, minute(Date(-18262))); +} + +TEST_F(DateTimeTest, minuteTimestampWithTimezone) { + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "minute(c0)", std::nullopt, std::nullopt)); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "minute(c0)", std::nullopt, "Asia/Kolkata")); + EXPECT_EQ( + 0, evaluateWithTimestampWithTimezone("minute(c0)", 0, "+00:00")); + EXPECT_EQ( + 30, + evaluateWithTimestampWithTimezone("minute(c0)", 0, "+05:30")); + EXPECT_EQ( + 6, + evaluateWithTimestampWithTimezone( + "minute(c0)", 4000000000000, "+00:00")); + EXPECT_EQ( + 36, + evaluateWithTimestampWithTimezone( + "minute(c0)", 4000000000000, "+05:30")); + EXPECT_EQ( + 4, + evaluateWithTimestampWithTimezone( + "minute(c0)", 998474645000, "+00:00")); + EXPECT_EQ( + 34, + evaluateWithTimestampWithTimezone( + "minute(c0)", 998474645000, "+05:30")); + EXPECT_EQ( + 59, + evaluateWithTimestampWithTimezone( + "minute(c0)", -1000, "+00:00")); + EXPECT_EQ( + 29, + evaluateWithTimestampWithTimezone( + "minute(c0)", -1000, "+05:30")); +} + +TEST_F(DateTimeTest, second) { + const auto second = [&](std::optional timestamp) { + return evaluateOnce("second(c0)", timestamp); + }; + EXPECT_EQ(std::nullopt, second(std::nullopt)); + EXPECT_EQ(0, second(Timestamp(0, 0))); + EXPECT_EQ(40, second(Timestamp(4000000000, 0))); + EXPECT_EQ(59, second(Timestamp(-1, 123000000))); + EXPECT_EQ(59, second(Timestamp(-1, 12300000000))); +} + +TEST_F(DateTimeTest, secondDate) { + const auto second = [&](std::optional date) { + return evaluateOnce("second(c0)", date); + }; + EXPECT_EQ(std::nullopt, second(std::nullopt)); + EXPECT_EQ(0, second(Date(0))); + EXPECT_EQ(0, second(Date(-1))); + EXPECT_EQ(0, second(Date(-40))); + EXPECT_EQ(0, second(Date(40))); + EXPECT_EQ(0, second(Date(18262))); + EXPECT_EQ(0, second(Date(-18262))); +} + +TEST_F(DateTimeTest, secondTimestampWithTimezone) { + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "second(c0)", std::nullopt, std::nullopt)); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "second(c0)", std::nullopt, "+05:30")); + EXPECT_EQ( + 0, evaluateWithTimestampWithTimezone("second(c0)", 0, "+00:00")); + EXPECT_EQ( + 0, evaluateWithTimestampWithTimezone("second(c0)", 0, "+05:30")); + EXPECT_EQ( + 40, + evaluateWithTimestampWithTimezone( + "second(c0)", 4000000000000, "+00:00")); + EXPECT_EQ( + 40, + evaluateWithTimestampWithTimezone( + "second(c0)", 4000000000000, "+05:30")); + EXPECT_EQ( + 59, + evaluateWithTimestampWithTimezone( + "second(c0)", -1000, "+00:00")); + EXPECT_EQ( + 59, + evaluateWithTimestampWithTimezone( + "second(c0)", -1000, "+05:30")); +} + +TEST_F(DateTimeTest, millisecond) { + const auto millisecond = [&](std::optional timestamp) { + return evaluateOnce("millisecond(c0)", timestamp); + }; + EXPECT_EQ(std::nullopt, millisecond(std::nullopt)); + EXPECT_EQ(0, millisecond(Timestamp(0, 0))); + EXPECT_EQ(0, millisecond(Timestamp(4000000000, 0))); + EXPECT_EQ(123, millisecond(Timestamp(-1, 123000000))); + EXPECT_EQ(12300, millisecond(Timestamp(-1, 12300000000))); +} + +TEST_F(DateTimeTest, millisecondDate) { + const auto millisecond = [&](std::optional date) { + return evaluateOnce("millisecond(c0)", date); + }; + EXPECT_EQ(std::nullopt, millisecond(std::nullopt)); + EXPECT_EQ(0, millisecond(Date(0))); + EXPECT_EQ(0, millisecond(Date(-1))); + EXPECT_EQ(0, millisecond(Date(-40))); + EXPECT_EQ(0, millisecond(Date(40))); + EXPECT_EQ(0, millisecond(Date(18262))); + EXPECT_EQ(0, millisecond(Date(-18262))); +} + +TEST_F(DateTimeTest, millisecondTimestampWithTimezone) { + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", std::nullopt, std::nullopt)); + EXPECT_EQ( + std::nullopt, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", std::nullopt, "+05:30")); + EXPECT_EQ( + 0, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", 0, "+00:00")); + EXPECT_EQ( + 0, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", 0, "+05:30")); + EXPECT_EQ( + 123, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", 4000000000123, "+00:00")); + EXPECT_EQ( + 123, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", 4000000000123, "+05:30")); + EXPECT_EQ( + 20, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", -980, "+00:00")); + EXPECT_EQ( + 20, + evaluateWithTimestampWithTimezone( + "millisecond(c0)", -980, "+05:30")); +} + +} // namespace +} // namespace facebook::velox::functions::sparksql::test diff --git a/velox/substrait/SubstraitToVeloxExpr.cpp b/velox/substrait/SubstraitToVeloxExpr.cpp index 42b99c91fad9..74f22a2c31f2 100644 --- a/velox/substrait/SubstraitToVeloxExpr.cpp +++ b/velox/substrait/SubstraitToVeloxExpr.cpp @@ -226,12 +226,13 @@ SubstraitVeloxExprConverter::toExtractExpr( std::vector> exprParams; exprParams.reserve(1); exprParams.emplace_back(params[1]); - if (from == "YEAR") { - // Use PrestoSql year function. + auto iter = extractDatetimeFunctionMap_.find(from); + if (iter != extractDatetimeFunctionMap_.end()) { return std::make_shared( - outputType, std::move(exprParams), "year"); + outputType, std::move(exprParams), iter->second); + } else { + VELOX_NYI("Extract from {} not supported.", from); } - VELOX_NYI("Extract from {} not supported.", from); } VELOX_FAIL("Constant is expected to be the first parameter in extract."); } diff --git a/velox/substrait/SubstraitToVeloxExpr.h b/velox/substrait/SubstraitToVeloxExpr.h index 66bc2141f644..d90d307d8116 100644 --- a/velox/substrait/SubstraitToVeloxExpr.h +++ b/velox/substrait/SubstraitToVeloxExpr.h @@ -105,6 +105,21 @@ class SubstraitVeloxExprConverter { /// The map storing the relations between the function id and the function /// name. std::unordered_map functionMap_; + + // The map storing the Substrait extract function input field and velox function name. + std::unordered_map extractDatetimeFunctionMap_ = { + {"MILLISECOND", "millisecond"}, + {"SECOND", "second"}, + {"MINUTE", "minute"}, + {"HOUR", "hour"}, + {"DAY", "day"}, + {"DAY_OF_WEEK", "day_of_week"}, + {"DAY_OF_YEAR", "day_of_year"}, + {"MONTH", "month"}, + {"QUARTER", "quarter"}, + {"YEAR", "year"}, + {"YEAR_OF_WEEK", "year_of_week"} + }; }; } // namespace facebook::velox::substrait