diff --git a/be/src/vec/core/call_on_type_index.h b/be/src/vec/core/call_on_type_index.h index b651fe2ec47502..e64e2003cb5643 100644 --- a/be/src/vec/core/call_on_type_index.h +++ b/be/src/vec/core/call_on_type_index.h @@ -276,6 +276,9 @@ bool call_on_index_and_number_data_type(TypeIndex number, F&& f) { case TypeIndex::Float64: return f(TypePair, T>()); + case TypeIndex::TimeV2: + return f(TypePair()); + case TypeIndex::Decimal32: return f(TypePair, T>()); case TypeIndex::Decimal64: diff --git a/be/src/vec/functions/function_cast.h b/be/src/vec/functions/function_cast.h index ab852b609becd7..2701b9aef72a56 100644 --- a/be/src/vec/functions/function_cast.h +++ b/be/src/vec/functions/function_cast.h @@ -499,8 +499,8 @@ struct ConvertImplToTimeType { using FromFieldType = typename FromDataType::FieldType; using ToFieldType = typename ToDataType::FieldType; - static Status execute(Block& block, const ColumnNumbers& arguments, uint32_t result, - size_t /*input_rows_count*/) { + static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments, + uint32_t result, size_t /*input_rows_count*/) { const ColumnWithTypeAndName& named_from = block.get_by_position(arguments[0]); using ColVecFrom = @@ -528,29 +528,53 @@ struct ConvertImplToTimeType { col_null_map_to = ColumnUInt8::create(size); auto& vec_null_map_to = col_null_map_to->get_data(); - UInt32 from_precision = 0; - UInt32 from_scale = 0; - UInt32 to_precision = NumberTraits::max_ascii_len(); - if constexpr (IsDecimalNumber) { - const auto& from_decimal_type = assert_cast(*named_from.type); - from_precision = from_decimal_type.get_precision(); - from_scale = from_decimal_type.get_scale(); + if constexpr (std::is_same_v) { + DateValueType current_date_value; + current_date_value.from_unixtime(context->state()->timestamp_ms() / 1000, + context->state()->timezone_obj()); + uint32_t scale = 0; + // Only DateTimeV2 has scale + if (std::is_same_v) { + scale = remove_nullable(block.get_by_position(result).type)->get_scale(); + } + // According to MySQL rules, when casting time type to date/datetime, + // the current date is added to the time + // So here we need to clear the time part + current_date_value.reset_time_part(); + for (size_t i = 0; i < size; ++i) { + auto& date_value = reinterpret_cast(vec_to[i]); + date_value = current_date_value; + int64_t microsecond = TimeValue::round_time(vec_from[i], scale); + // Only TimeV2 type needs microseconds + if constexpr (IsTimeV2Type) { + vec_null_map_to[i] = !date_value.template date_add_interval( + TimeInterval {MICROSECOND, microsecond, false}); + } else { + vec_null_map_to[i] = + !date_value.template date_add_interval(TimeInterval { + SECOND, microsecond / TimeValue::ONE_SECOND_MICROSECONDS, + false}); + } + + // DateType of VecDateTimeValue should cast to date + if constexpr (IsDateType) { + date_value.cast_to_date(); + } else if constexpr (IsDateTimeType) { + date_value.to_datetime(); + } + } + } else { + for (size_t i = 0; i < size; ++i) { + auto& date_value = reinterpret_cast(vec_to[i]); + vec_null_map_to[i] = !date_value.from_date_int64(int64_t(vec_from[i])); + // DateType of VecDateTimeValue should cast to date + if constexpr (IsDateType) { + date_value.cast_to_date(); + } else if constexpr (IsDateTimeType) { + date_value.to_datetime(); + } + } } - bool narrow_integral = to_precision < (from_precision - from_scale); - std::visit( - [&](auto narrow_integral) { - for (size_t i = 0; i < size; ++i) { - auto& date_value = reinterpret_cast(vec_to[i]); - vec_null_map_to[i] = !date_value.from_date_int64(int64_t(vec_from[i])); - // DateType of VecDateTimeValue should cast to date - if constexpr (IsDateType) { - date_value.cast_to_date(); - } else if constexpr (IsDateTimeType) { - date_value.to_datetime(); - } - } - }, - make_bool_variant(narrow_integral)); block.get_by_position(result).column = ColumnNullable::create(std::move(col_to), std::move(col_null_map_to)); } else { @@ -1531,7 +1555,7 @@ class FunctionConvertToTimeType : public IFunction { using RightDataType = typename Types::RightType; ret_status = ConvertImplToTimeType::execute( - block, arguments, result, input_rows_count); + context, block, arguments, result, input_rows_count); return true; }; diff --git a/be/src/vec/runtime/time_value.h b/be/src/vec/runtime/time_value.h index eaefa3b7658597..a07f5cb2e0e03f 100644 --- a/be/src/vec/runtime/time_value.h +++ b/be/src/vec/runtime/time_value.h @@ -35,10 +35,20 @@ class TimeValue { constexpr static int64_t ONE_HOUR_MICROSECONDS = 60 * ONE_MINUTE_MICROSECONDS; constexpr static int64_t ONE_MINUTE_SECONDS = 60; constexpr static int64_t ONE_HOUR_SECONDS = 60 * ONE_MINUTE_SECONDS; + constexpr static uint32_t MICROS_SCALE = 6; using TimeType = typename PrimitiveTypeTraits::CppType; using ColumnTime = vectorized::DataTypeTimeV2::ColumnType; + static int64_t round_time(TimeType value, uint32_t scale) { + int64_t time = value; + DCHECK(scale <= MICROS_SCALE); + int64_t factor = std::pow(10, 6 - scale); + int64_t roundedValue = (time >= 0) ? (time + factor / 2) / factor * factor + : (time - factor / 2) / factor * factor; + return roundedValue; + } + // refer to https://dev.mysql.com/doc/refman/5.7/en/time.html // the time value between '-838:59:59' and '838:59:59' /// TODO: Why is the time type stored as double? Can we directly use int64 and remove the time limit? diff --git a/be/src/vec/runtime/vdatetime_value.h b/be/src/vec/runtime/vdatetime_value.h index 680d53d2e6960a..f72bd70c6bf83e 100644 --- a/be/src/vec/runtime/vdatetime_value.h +++ b/be/src/vec/runtime/vdatetime_value.h @@ -446,6 +446,12 @@ class VecDateTimeValue { // Now this type is a temp solution with little changes return _hour * SECOND_PER_HOUR + _minute * SECOND_PER_MINUTE + _second; } + void reset_time_part() { + _hour = 0; + _minute = 0; + _second = 0; + } + bool check_loss_accuracy_cast_to_date() { auto loss_accuracy = _hour != 0 || _minute != 0 || _second != 0; cast_to_date(); @@ -960,6 +966,15 @@ class DateV2Value { return hour() * SECOND_PER_HOUR + minute() * SECOND_PER_MINUTE + second(); } + void reset_time_part() { + if constexpr (is_datetime) { + date_v2_value_.hour_ = 0; + date_v2_value_.minute_ = 0; + date_v2_value_.second_ = 0; + date_v2_value_.microsecond_ = 0; + } + } + int64_t time_part_to_microsecond() const { return time_part_to_seconds() * 1000 * 1000 + microsecond(); } diff --git a/be/test/runtime/time_value_test.cpp b/be/test/runtime/time_value_test.cpp new file mode 100644 index 00000000000000..d38698131f23d9 --- /dev/null +++ b/be/test/runtime/time_value_test.cpp @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +namespace doris { + +TEST(TimeValueTest, make_time) { + int64_t hour = 1; + int64_t minute = 2; + int64_t second = 3; + TimeValue::TimeType time = TimeValue::make_time(hour, minute, second); + EXPECT_EQ(time, 3723000000); +} + +TEST(TimeValueTest, round_time) { + // 01:02:03.500000 -> 01:02:04.000000 + EXPECT_EQ(TimeValue::round_time(TimeValue::make_time(1, 2, 3, 500000), 0), + TimeValue::make_time(1, 2, 4)); + + // 01:02:03.499999 -> 01:01:03.000000 + EXPECT_EQ(TimeValue::round_time(TimeValue::make_time(1, 2, 3, 499999), 0), + TimeValue::make_time(1, 2, 3)); + + // -01:02:03.500000 -> -01:01:04.000000 + EXPECT_EQ(TimeValue::round_time(-TimeValue::make_time(1, 2, 3, 500000), 0), + -TimeValue::make_time(1, 2, 4)); + + // -01:02:03.499999 -> -01:01:03.000000 + EXPECT_EQ(TimeValue::round_time(-TimeValue::make_time(1, 2, 3, 499999), 0), + -TimeValue::make_time(1, 2, 3)); +} +} // namespace doris \ No newline at end of file diff --git a/be/test/vec/function/function_time_cast_to_date_test.cpp b/be/test/vec/function/function_time_cast_to_date_test.cpp new file mode 100644 index 00000000000000..04ad5bf1f6c0c8 --- /dev/null +++ b/be/test/vec/function/function_time_cast_to_date_test.cpp @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +#include + +#include "testutil/column_helper.h" +#include "testutil/mock/mock_runtime_state.h" +#include "vec/data_types/data_type.h" +#include "vec/data_types/data_type_nullable.h" +#include "vec/data_types/data_type_time_v2.h" +#include "vec/functions/function_cast.h" + +namespace doris::vectorized { + +struct MockFunctionCastFromTimeToDateName { + static constexpr auto name = "FunctionCastFromTimeToDate"; +}; + +struct FunctionCastFromTimeToDate : public ::testing::Test { + void SetUp() override { + // 2025-3-25 11:45:14 + state._timestamp_ms = 1742874314000; + context._state = &state; + to_type = nullptr; + from_data.clear(); + expected.clear(); + } + + template + void test() { + using Convert = + ConvertImplToTimeType; + + Block block = ColumnHelper::create_block(from_data); + block.insert(ColumnWithTypeAndName {nullptr, to_type, "to"}); + auto st = Convert::execute(&context, block, ColumnNumbers {0}, 1, from_data.size()); + + EXPECT_TRUE(st) << st.ok(); + for (int i = 0; i < expected.size(); i++) { + auto column = block.get_by_position(1).column; + EXPECT_EQ(to_type->to_string(*column, i), expected[i]) + << " time value : " << DataTypeTimeV2 {}.to_string(from_data[i]); + } + } + + void add_case(TimeValue::TimeType time, std::string str) { + from_data.push_back(time); + expected.push_back(str); + } + + using FromType = DataTypeDateTimeV2; + MockRuntimeState state; + FunctionContext context; + + DataTypePtr to_type; + std::vector from_data; + std::vector expected; +}; + +TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_6) { + to_type = std::make_shared(std::make_shared(6)); + add_case(TimeValue::make_time(1, 2, 3, 123456), "2025-03-25 01:02:03.123456"); + add_case(TimeValue::make_time(1, 2, 3, 654321), "2025-03-25 01:02:03.654321"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:56.876544"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56.345679"); + test(); +} + +TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_3) { + to_type = std::make_shared(std::make_shared(3)); + add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03.124"); + add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00.000"); + add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03.123"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:56.877"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56.346"); + test(); +} + +TEST_F(FunctionCastFromTimeToDate, time_to_datetimev2_0) { + to_type = std::make_shared(std::make_shared(0)); + add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03"); + add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00"); + add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:57"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56"); + test(); +} + +TEST_F(FunctionCastFromTimeToDate, time_to_datetimev1_0) { + to_type = std::make_shared(std::make_shared()); + add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25 01:02:03"); + add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25 01:03:00"); + add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25 01:02:03"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24 22:57:57"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24 22:57:56"); + test(); +} + +TEST_F(FunctionCastFromTimeToDate, time_to_datetv2) { + to_type = std::make_shared(std::make_shared()); + add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25"); + add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25"); + add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24"); + test(); +} + +TEST_F(FunctionCastFromTimeToDate, time_to_datetv1) { + to_type = std::make_shared(std::make_shared()); + add_case(TimeValue::make_time(1, 2, 3, 123500), "2025-03-25"); + add_case(TimeValue::make_time(1, 2, 59, 999500), "2025-03-25"); + add_case(TimeValue::make_time(1, 2, 3, 123499), "2025-03-25"); + add_case(-TimeValue::make_time(1, 2, 3, 123456), "2025-03-24"); + add_case(-TimeValue::make_time(1, 2, 3, 654321), "2025-03-24"); + test(); +} +} // namespace doris::vectorized \ No newline at end of file diff --git a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java index fbd9bf6924ea39..73c7e2cb31b290 100644 --- a/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java +++ b/fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java @@ -620,13 +620,21 @@ public static ImmutableSetMultimap getImplicitCast builder.put(TIME, DOUBLE); builder.put(TIME, VARCHAR); builder.put(TIME, STRING); + builder.put(TIME, DATE); + builder.put(TIME, DATETIME); + builder.put(TIME, DATEV2); + builder.put(TIME, DATETIMEV2); - //TIMEV2 + // TIMEV2 builder.put(TIMEV2, TIME); builder.put(TIMEV2, TIMEV2); builder.put(TIMEV2, DOUBLE); builder.put(TIMEV2, VARCHAR); builder.put(TIMEV2, STRING); + builder.put(TIMEV2, DATE); + builder.put(TIMEV2, DATETIME); + builder.put(TIMEV2, DATEV2); + builder.put(TIMEV2, DATETIMEV2); implicitCastMap = builder.build(); } diff --git a/regression-test/suites/correctness/test_cast_time_to_datetime.groovy b/regression-test/suites/correctness/test_cast_time_to_datetime.groovy new file mode 100644 index 00000000000000..e8908d3755e948 --- /dev/null +++ b/regression-test/suites/correctness/test_cast_time_to_datetime.groovy @@ -0,0 +1,22 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +suite("test_cast_time_to_datetime") { + def result1 = sql """ select datediff(now(), from_unixtime(cast(1742194502 as bigint),'yyyy-MM-dd HH:mm:ss')); """ + def result2 = sql """ select datediff(current_time(), from_unixtime(cast(1742194502 as bigint),'yyyy-MM-dd HH:mm:ss')); """ + assertEquals(result1[0][0], result2[0][0], "The results of the two SQL queries should be the same.") +} \ No newline at end of file