diff --git a/be/src/runtime/primitive_type.cpp b/be/src/runtime/primitive_type.cpp index 9e665f6bbe79ce..020d4acaba59cd 100644 --- a/be/src/runtime/primitive_type.cpp +++ b/be/src/runtime/primitive_type.cpp @@ -26,6 +26,14 @@ namespace doris { PrimitiveType thrift_to_type(TPrimitiveType::type ttype) { + // TIME (v1) used thrift enum value 18 before it was removed from the IDL. Old FEs can still + // send that value during a rolling upgrade. Treat it as TIMEV2, which has the same value + // representation, instead of falling through to the fatal unknown-type check below. + constexpr auto legacy_thrift_time = static_cast(18); + if (ttype == legacy_thrift_time) { + return TYPE_TIMEV2; + } + switch (ttype) { case TPrimitiveType::INVALID_TYPE: return INVALID_TYPE; diff --git a/be/src/vec/functions/function_convert_tz.cpp b/be/src/vec/functions/function_convert_tz.cpp index b3af851e9404c7..c415f6891800e3 100644 --- a/be/src/vec/functions/function_convert_tz.cpp +++ b/be/src/vec/functions/function_convert_tz.cpp @@ -65,10 +65,6 @@ struct ConvertTzState { }; class FunctionConvertTZ : public IFunction { - constexpr static PrimitiveType PType = PrimitiveType::TYPE_DATETIMEV2; - using DateValueType = PrimitiveTypeTraits::CppType; - using ColumnType = PrimitiveTypeTraits::ColumnType; - public: static constexpr auto name = "convert_tz"; @@ -136,6 +132,34 @@ class FunctionConvertTZ : public IFunction { Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t input_rows_count) const override { + const auto argument_type = + remove_nullable(block.get_by_position(arguments[0]).type)->get_primitive_type(); + const auto result_type = + remove_nullable(block.get_by_position(result).type)->get_primitive_type(); + if (argument_type == TYPE_DATETIME && result_type == TYPE_DATETIME) { + return execute_typed(context, block, arguments, result, + input_rows_count); + } + if (argument_type == TYPE_DATETIMEV2 && result_type == TYPE_DATETIMEV2) { + return execute_typed(context, block, arguments, result, + input_rows_count); + } + return Status::InternalError("Invalid argument/result types {}/{} for function {}", + block.get_by_position(arguments[0]).type->get_name(), + block.get_by_position(result).type->get_name(), name); + } + +private: + template + using DateValue = PrimitiveTypeTraits::CppType; + + template + using DateColumn = PrimitiveTypeTraits::ColumnType; + + template + Status execute_typed(FunctionContext* context, Block& block, const ColumnNumbers& arguments, + uint32_t result, size_t input_rows_count) const { + using ColumnType = PrimitiveTypeTraits::ColumnType; auto* convert_tz_state = reinterpret_cast( context->get_function_state(FunctionContext::FRAGMENT_LOCAL)); if (!convert_tz_state) { @@ -172,23 +196,24 @@ class FunctionConvertTZ : public IFunction { auto result_column = ColumnType::create(); if (convert_tz_state->use_state) { // ignore argument columns, use cached timezone input in state - execute_tz_const_with_state(convert_tz_state, - assert_cast(argument_columns[0].get()), - assert_cast(result_column.get()), - result_null_map, input_rows_count); + execute_tz_const_with_state( + convert_tz_state, assert_cast(argument_columns[0].get()), + assert_cast(result_column.get()), result_null_map, + input_rows_count); } else if (col_const[1] && col_const[2]) { // arguments are const - execute_tz_const(context, assert_cast(argument_columns[0].get()), - assert_cast(argument_columns[1].get()), - assert_cast(argument_columns[2].get()), - assert_cast(result_column.get()), result_null_map, - input_rows_count); + execute_tz_const(context, + assert_cast(argument_columns[0].get()), + assert_cast(argument_columns[1].get()), + assert_cast(argument_columns[2].get()), + assert_cast(result_column.get()), result_null_map, + input_rows_count); } else { - _execute(context, assert_cast(argument_columns[0].get()), - assert_cast(argument_columns[1].get()), - assert_cast(argument_columns[2].get()), - assert_cast(result_column.get()), result_null_map, - input_rows_count); + _execute(context, assert_cast(argument_columns[0].get()), + assert_cast(argument_columns[1].get()), + assert_cast(argument_columns[2].get()), + assert_cast(result_column.get()), result_null_map, + input_rows_count); } //if const if (block.get_data_type(result)->is_nullable()) { @@ -200,10 +225,10 @@ class FunctionConvertTZ : public IFunction { return Status::OK(); } -private: - static void _execute(FunctionContext* context, const ColumnType* date_column, + template + static void _execute(FunctionContext* context, const DateColumn* date_column, const ColumnString* from_tz_column, const ColumnString* to_tz_column, - ColumnType* result_column, NullMap& result_null_map, + DateColumn* result_column, NullMap& result_null_map, size_t input_rows_count) { for (size_t i = 0; i < input_rows_count; i++) { if (result_null_map[i]) { @@ -212,12 +237,14 @@ class FunctionConvertTZ : public IFunction { } auto from_tz = from_tz_column->get_data_at(i).to_string(); auto to_tz = to_tz_column->get_data_at(i).to_string(); - execute_inner_loop(date_column, from_tz, to_tz, result_column, result_null_map, i); + execute_inner_loop(date_column, from_tz, to_tz, result_column, result_null_map, + i); } } + template static std::pair unix_timestamp_for_convert_tz( - const DateValueType& ts_value, const cctz::time_zone& from_tz) { + const DateValue& ts_value, const cctz::time_zone& from_tz) { cctz::civil_second civil_time(ts_value.year(), ts_value.month(), ts_value.day(), ts_value.hour(), ts_value.minute(), ts_value.second()); const auto lookup = from_tz.lookup(civil_time); @@ -226,13 +253,19 @@ class FunctionConvertTZ : public IFunction { // Skipped civil times map to the transition instant. Do not keep the // input fractional part inside a local time interval that never existed. - return {tp.time_since_epoch().count(), skipped ? 0 : ts_value.microsecond()}; + int64_t microsecond = 0; + if constexpr (PType == TYPE_DATETIMEV2) { + microsecond = ts_value.microsecond(); + } + return {tp.time_since_epoch().count(), skipped ? 0 : microsecond}; } + template static void execute_tz_const_with_state(ConvertTzState* convert_tz_state, - const ColumnType* date_column, - ColumnType* result_column, NullMap& result_null_map, - size_t input_rows_count) { + const DateColumn* date_column, + DateColumn* result_column, + NullMap& result_null_map, size_t input_rows_count) { + using DateValueType = DateValue; cctz::time_zone& from_tz = convert_tz_state->from_tz; cctz::time_zone& to_tz = convert_tz_state->to_tz; auto push_null = [&](size_t row) { @@ -256,20 +289,27 @@ class FunctionConvertTZ : public IFunction { DateValueType ts_value = date_column->get_element(i); DateValueType ts_value2; - ts_value2.from_unixtime(unix_timestamp_for_convert_tz(ts_value, from_tz), to_tz); + const auto unix_time = unix_timestamp_for_convert_tz(ts_value, from_tz); + if constexpr (PType == TYPE_DATETIME) { + ts_value2.from_unixtime(unix_time.first, to_tz); + ts_value2.set_type(TIME_DATETIME); + } else { + ts_value2.from_unixtime(unix_time, to_tz); + } if (!ts_value2.is_valid_date()) [[unlikely]] { throw_out_of_bound_convert_tz(date_column->get_element(i), from_tz.name(), to_tz.name()); } - result_column->insert(Field::create_field(ts_value2)); + result_column->insert(Field::create_field(ts_value2)); } } - static void execute_tz_const(FunctionContext* context, const ColumnType* date_column, + template + static void execute_tz_const(FunctionContext* context, const DateColumn* date_column, const ColumnString* from_tz_column, - const ColumnString* to_tz_column, ColumnType* result_column, + const ColumnString* to_tz_column, DateColumn* result_column, NullMap& result_null_map, size_t input_rows_count) { auto from_tz = from_tz_column->get_data_at(0).to_string(); auto to_tz = to_tz_column->get_data_at(0).to_string(); @@ -287,13 +327,17 @@ class FunctionConvertTZ : public IFunction { result_column->insert_default(); continue; } - execute_inner_loop(date_column, from_tz, to_tz, result_column, result_null_map, i); + execute_inner_loop(date_column, from_tz, to_tz, result_column, result_null_map, + i); } } - static void execute_inner_loop(const ColumnType* date_column, const std::string& from_tz_name, - const std::string& to_tz_name, ColumnType* result_column, - NullMap& result_null_map, const size_t index_now) { + template + static void execute_inner_loop(const DateColumn* date_column, + const std::string& from_tz_name, const std::string& to_tz_name, + DateColumn* result_column, NullMap& result_null_map, + const size_t index_now) { + using DateValueType = DateValue; DateValueType ts_value = date_column->get_element(index_now); cctz::time_zone from_tz {}, to_tz {}; DateValueType ts_value2; @@ -307,14 +351,20 @@ class FunctionConvertTZ : public IFunction { to_tz_name); } - ts_value2.from_unixtime(unix_timestamp_for_convert_tz(ts_value, from_tz), to_tz); + const auto unix_time = unix_timestamp_for_convert_tz(ts_value, from_tz); + if constexpr (PType == TYPE_DATETIME) { + ts_value2.from_unixtime(unix_time.first, to_tz); + ts_value2.set_type(TIME_DATETIME); + } else { + ts_value2.from_unixtime(unix_time, to_tz); + } if (!ts_value2.is_valid_date()) [[unlikely]] { throw_out_of_bound_convert_tz(date_column->get_element(index_now), from_tz.name(), to_tz.name()); } - result_column->insert(Field::create_field(ts_value2)); + result_column->insert(Field::create_field(ts_value2)); } }; diff --git a/be/src/vec/functions/function_date_or_datetime_computation.h b/be/src/vec/functions/function_date_or_datetime_computation.h index 06f7514ce73f3b..76626edf53e122 100644 --- a/be/src/vec/functions/function_date_or_datetime_computation.h +++ b/be/src/vec/functions/function_date_or_datetime_computation.h @@ -1011,25 +1011,34 @@ struct CurrentDateTimeImpl { static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t input_rows_count) { + const auto result_type = block.get_by_position(result).type->get_primitive_type(); if constexpr (WithPrecision) { - DCHECK(block.get_by_position(result).type->get_primitive_type() == TYPE_DATETIMEV2 || - block.get_by_position(result).type->get_primitive_type() == TYPE_DATEV2); - if (block.get_by_position(result).type->get_primitive_type() == TYPE_DATETIMEV2) { + if (result_type == TYPE_DATETIMEV2) { return executeImpl(context, block, arguments, result, input_rows_count); - } else { + } + if (result_type == TYPE_DATEV2) { return executeImpl(context, block, arguments, result, input_rows_count); } } else { - if (block.get_by_position(result).type->get_primitive_type() == TYPE_DATETIMEV2) { + if (result_type == TYPE_DATETIMEV2) { return executeImpl(context, block, arguments, result, input_rows_count); - } else { + } + if (result_type == TYPE_DATEV2) { return executeImpl(context, block, arguments, result, input_rows_count); } + if (result_type == TYPE_DATETIME) { + // Keep compatibility with plans generated by an old FE, where now() without a + // precision argument has a DATETIME (v1) result type. + return executeImpl(context, block, arguments, result, + input_rows_count); + } } + return Status::InternalError("Invalid result type {} for function {}", + block.get_by_position(result).type->get_name(), name); } template @@ -1320,6 +1329,26 @@ struct UtcImpl { static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t input_rows_count) { + const auto result_type = block.get_by_position(result).type->get_primitive_type(); + if (result_type == UTCType) { + return execute_impl(context, block, arguments, result, input_rows_count); + } + if constexpr (UTCType == TYPE_DATETIMEV2) { + // In 3.1.x, only utc_timestamp exists and its result type is DATETIME (v1). + if (result_type == TYPE_DATETIME) { + return execute_impl(context, block, arguments, result, + input_rows_count); + } + } + return Status::InternalError("Invalid result type {} for function {}", + block.get_by_position(result).type->get_name(), name); + } + +private: + template + static Status execute_impl(FunctionContext* context, Block& block, + const ColumnNumbers& arguments, uint32_t result, + size_t input_rows_count) { int scale = 0; if (arguments.size() == 1) { // the precision must be const, which is checked in fe. @@ -1327,30 +1356,40 @@ struct UtcImpl { block.get_by_position(arguments[0]).column.get()); scale = col->get_element(0); } - auto col_to = PrimitiveTypeTraits::ColumnType::create(); + auto col_to = PrimitiveTypeTraits::ColumnType::create(); DateV2Value dtv; if (dtv.from_unixtime(context->state()->timestamp_ms() / 1000, context->state()->nano_seconds(), "+00:00", scale)) { - if constexpr (ReturnType == TYPE_DATETIMEV2) { + if constexpr (ResultType == TYPE_DATETIMEV2) { col_to->insert_data(reinterpret_cast(&dtv), 0); - } else if constexpr (ReturnType == TYPE_DATEV2) { + } else if constexpr (ResultType == TYPE_DATEV2) { DateV2Value dv; dv.assign_from(dtv); col_to->insert_data(reinterpret_cast(&dv), 0); - } else if constexpr (ReturnType == TYPE_TIMEV2) { + } else if constexpr (ResultType == TYPE_TIMEV2) { double time = TimeValue::make_time(dtv.hour(), dtv.minute(), dtv.second(), dtv.microsecond()); col_to->insert_data(reinterpret_cast(&time), 0); + } else { + static_assert(ResultType == TYPE_DATETIME); + VecDateTimeValue dv; + dv.from_unixtime(context->state()->timestamp_ms() / 1000, "+00:00"); + dv.set_type(TIME_DATETIME); + col_to->insert_data(reinterpret_cast(&dv), 0); } } else { - if constexpr (ReturnType == TYPE_DATEV2) { + if constexpr (ResultType == TYPE_DATEV2) { uint32_t invalid_val = 0; col_to->insert_data(reinterpret_cast(&invalid_val), 0); - } else if constexpr (ReturnType == TYPE_DATETIMEV2) { + } else if constexpr (ResultType == TYPE_DATETIMEV2) { uint64_t invalid_val = 0; col_to->insert_data(reinterpret_cast(&invalid_val), 0); + } else if constexpr (ResultType == TYPE_DATETIME) { + VecDateTimeValue invalid_val; + col_to->insert_data(reinterpret_cast(&invalid_val), 0); } else { - typename PrimitiveTypeTraits::CppType invalid_val = 0; + static_assert(ResultType == TYPE_TIMEV2); + typename PrimitiveTypeTraits::CppType invalid_val = 0; col_to->insert_data(reinterpret_cast(&invalid_val), 0); } } @@ -1384,15 +1423,19 @@ class CurrentDateFunctionBuilder : public FunctionBuilderImpl { for (size_t i = 0; i < arguments.size(); ++i) { data_types[i] = arguments[i].type; } - if (return_type->get_primitive_type() == TYPE_DATEV2) { + const auto result_type = return_type->get_primitive_type(); + if (result_type == TYPE_DATEV2) { auto function = FunctionCurrentDateOrDateTime< CurrentDateImpl>::create(); return std::make_shared(function, data_types, return_type); - } else { + } + if (result_type == TYPE_DATE) { auto function = FunctionCurrentDateOrDateTime< - CurrentDateImpl>::create(); + CurrentDateImpl>::create(); return std::make_shared(function, data_types, return_type); } + throw doris::Exception(ErrorCode::INTERNAL_ERROR, "Invalid result type {} for function {}", + return_type->get_name(), FunctionName::name); } }; diff --git a/be/src/vec/functions/function_other_types_to_date.cpp b/be/src/vec/functions/function_other_types_to_date.cpp index f914a430519eee..f3a36ad7567017 100644 --- a/be/src/vec/functions/function_other_types_to_date.cpp +++ b/be/src/vec/functions/function_other_types_to_date.cpp @@ -242,7 +242,6 @@ struct StrToDate { struct MakeDateImpl { static constexpr auto name = "makedate"; - using DateValueType = PrimitiveTypeTraits::CppType; static bool is_variadic() { return false; } @@ -292,17 +291,36 @@ struct MakeDateImpl { const auto* year_col = assert_cast(argument_columns[0].get()); const auto* dayofyear_col = assert_cast(argument_columns[1].get()); - ColumnPtr res_column; + const auto result_type = block.get_by_position(result).type->get_primitive_type(); + if (result_type == TYPE_DATE) { + return execute_typed(block, result, input_rows_count, year_col, + dayofyear_col, col_const[1], result_null_map_column, + result_null_map); + } + if (result_type == TYPE_DATEV2) { + return execute_typed(block, result, input_rows_count, year_col, + dayofyear_col, col_const[1], result_null_map_column, + result_null_map); + } + return Status::InternalError("Invalid result type {} for function {}", + block.get_by_position(result).type->get_name(), name); + } - res_column = ColumnDateV2::create(input_rows_count); - if (col_const[1]) { - execute_impl_right_const( - year_col->get_data(), dayofyear_col->get_element(0), result_null_map, - static_cast(res_column->assume_mutable().get())->get_data()); +private: + template + static Status execute_typed(Block& block, uint32_t result, size_t input_rows_count, + const ColumnInt32* year_col, const ColumnInt32* dayofyear_col, + bool dayofyear_const, + ColumnBool::MutablePtr& result_null_map_column, + const NullMap& result_null_map) { + auto res_column = ColumnVector::create(input_rows_count); + auto& result_data = res_column->get_data(); + if (dayofyear_const) { + execute_impl_right_const(year_col->get_data(), dayofyear_col->get_element(0), + result_null_map, result_data); } else { - execute_impl( - year_col->get_data(), dayofyear_col->get_data(), result_null_map, - static_cast(res_column->assume_mutable().get())->get_data()); + execute_impl(year_col->get_data(), dayofyear_col->get_data(), result_null_map, + result_data); } // Wrap result in nullable column only if input has nullable arguments @@ -316,7 +334,7 @@ struct MakeDateImpl { return Status::OK(); } -private: + template static void execute_impl(const PaddedPODArray& year_data, const PaddedPODArray& dayofyear_data, const NullMap& result_null_map, PaddedPODArray& res) { @@ -333,10 +351,11 @@ struct MakeDateImpl { if (dayofyear <= 0 || year < 0 || year > 9999) [[unlikely]] { throw_out_of_bound_two_ints(name, year, dayofyear); } - _execute_inner_loop(year, dayofyear, res, i); + _execute_inner_loop(year, dayofyear, res[i]); } } + template static void execute_impl_right_const(const PaddedPODArray& year_data, Int32 dayofyear, const NullMap& result_null_map, PaddedPODArray& res) { @@ -352,18 +371,24 @@ struct MakeDateImpl { if (dayofyear <= 0 || year < 0 || year > 9999) [[unlikely]] { throw_out_of_bound_two_ints(name, year, dayofyear); } - _execute_inner_loop(year, dayofyear, res, i); + _execute_inner_loop(year, dayofyear, res[i]); } } - static void _execute_inner_loop(const int& year, const int& dayofyear, - PaddedPODArray& res, size_t index) { - auto& res_val = *reinterpret_cast(&res[index]); - res_val.unchecked_set_time(year, 1, 1, 0, 0, 0, 0); + template + static void _execute_inner_loop(int year, int dayofyear, DateValueType& res_val) { + if constexpr (std::is_same_v) { + res_val.unchecked_set_time(year, 1, 1, 0, 0, 0); + } else { + res_val.unchecked_set_time(year, 1, 1, 0, 0, 0, 0); + } TimeInterval interval(DAY, dayofyear - 1, false); if (!res_val.template date_add_interval(interval)) { throw_out_of_bound_two_ints(name, year, dayofyear); } + if constexpr (std::is_same_v) { + res_val.cast_to_date(); + } } }; diff --git a/be/test/vec/function/function_test_util.h b/be/test/vec/function/function_test_util.h index abe896572d23e8..d2d34ee4c1bfa7 100644 --- a/be/test/vec/function/function_test_util.h +++ b/be/test/vec/function/function_test_util.h @@ -265,6 +265,8 @@ DataTypePtr get_return_type_descriptor(int scale, int precision) { } else if constexpr (std::is_same_v) { return DataTypeFactory::instance().create_data_type(doris::PrimitiveType::TYPE_DATETIME, false); + } else if constexpr (std::is_same_v) { + return DataTypeFactory::instance().create_data_type(doris::PrimitiveType::TYPE_DATE, false); } else if (std::is_same_v) { return DataTypeFactory::instance().create_data_type(doris::PrimitiveType::TYPE_DATEV2, false); diff --git a/be/test/vec/function/function_time_test.cpp b/be/test/vec/function/function_time_test.cpp index 015bc15f0c3fca..0f481f0e8308f6 100644 --- a/be/test/vec/function/function_time_test.cpp +++ b/be/test/vec/function/function_time_test.cpp @@ -324,6 +324,16 @@ TEST(VTimestampFunctionsTest, convert_tz_test) { std::string("0000-01-01 00:00:00")}}; static_cast( check_function(func_name, input_types, data_set)); + + // A 3.1 FE uses DATETIME (v1) for both the argument and result of convert_tz. + InputTypeSet legacy_input_types = {PrimitiveType::TYPE_DATETIME, + Consted {PrimitiveType::TYPE_VARCHAR}, + Consted {PrimitiveType::TYPE_VARCHAR}}; + DataSet legacy_data_set = {{{std::string {"2019-08-01 02:18:27"}, + std::string {"Asia/Shanghai"}, std::string {"UTC"}}, + std::string("2019-07-31 18:18:27")}}; + static_cast(check_function(func_name, legacy_input_types, + legacy_data_set)); } { @@ -458,6 +468,8 @@ TEST(VTimestampFunctionsTest, makedate_test) { }; static_cast(check_function(func_name, input_types, data_set)); + // A 3.1 FE sends the same function and arguments with a DATE (v1) result slot. + static_cast(check_function(func_name, input_types, data_set)); } TEST(VTimestampFunctionsTest, weekday_test) { @@ -2572,4 +2584,157 @@ TEST(VTimestampFunctionsTest, add_union_functions_cover_all) { } } +TEST(VTimestampFunctionsTest, now_legacy_datetime_result_for_old_plan) { + ColumnsWithTypeAndName arguments; + auto legacy_datetime_type = std::make_shared(); + auto now = + SimpleFunctionFactory::instance().get_function("now", arguments, legacy_datetime_type); + ASSERT_NE(now, nullptr); + + FunctionUtils fn_utils(legacy_datetime_type, {}, false); + auto* fn_ctx = fn_utils.get_fn_ctx(); + ASSERT_TRUE(now->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); + ASSERT_TRUE(now->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + + Block now_block {{nullptr, legacy_datetime_type, "now"}}; + ASSERT_TRUE(now->execute(fn_ctx, now_block, {}, 0, 1).ok()); + const auto* now_const = + check_and_get_column(now_block.get_by_position(0).column.get()); + ASSERT_NE(now_const, nullptr); + ASSERT_NE(check_and_get_column(&now_const->get_data_column()), nullptr); + + // This is the expression shape generated by an old FE for + // CAST(NOW() AS DATETIMEV2(0)). Before the compatibility fix, now() returned a physical + // ColumnDateV2 here, and the outer DateTime-to-DateTimeV2 cast dereferenced a null downcast. + auto datetimev2_type = std::make_shared(0); + ColumnsWithTypeAndName cast_arguments { + {now_block.get_by_position(0).column, legacy_datetime_type, "now"}, + {nullptr, datetimev2_type, "target_type"}}; + auto cast = + SimpleFunctionFactory::instance().get_function("CAST", cast_arguments, datetimev2_type); + ASSERT_NE(cast, nullptr); + Block cast_block {cast_arguments}; + cast_block.insert({nullptr, datetimev2_type, "result"}); + ASSERT_TRUE(cast->execute(fn_ctx, cast_block, {0}, 2, 1).ok()); + const auto* result_const = + check_and_get_column(cast_block.get_by_position(2).column.get()); + ASSERT_NE(result_const, nullptr); + EXPECT_NE(check_and_get_column(&result_const->get_data_column()), nullptr); + + EXPECT_TRUE(now->close(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + EXPECT_TRUE(now->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); +} + +TEST(VTimestampFunctionsTest, curdate_legacy_date_result_for_old_plan) { + ColumnsWithTypeAndName arguments; + auto legacy_date_type = std::make_shared(); + auto curdate = + SimpleFunctionFactory::instance().get_function("curdate", arguments, legacy_date_type); + ASSERT_NE(curdate, nullptr); + + FunctionUtils fn_utils(legacy_date_type, {}, false); + auto* fn_ctx = fn_utils.get_fn_ctx(); + ASSERT_TRUE(curdate->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); + ASSERT_TRUE(curdate->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + + Block curdate_block {{nullptr, legacy_date_type, "curdate"}}; + ASSERT_TRUE(curdate->execute(fn_ctx, curdate_block, {}, 0, 1).ok()); + const auto* curdate_const = + check_and_get_column(curdate_block.get_by_position(0).column.get()); + ASSERT_NE(curdate_const, nullptr); + ASSERT_NE(check_and_get_column(&curdate_const->get_data_column()), nullptr); + + auto datev2_type = std::make_shared(); + ColumnsWithTypeAndName cast_arguments { + {curdate_block.get_by_position(0).column, legacy_date_type, "curdate"}, + {nullptr, datev2_type, "target_type"}}; + auto cast = SimpleFunctionFactory::instance().get_function("CAST", cast_arguments, datev2_type); + ASSERT_NE(cast, nullptr); + Block cast_block {cast_arguments}; + cast_block.insert({nullptr, datev2_type, "result"}); + ASSERT_TRUE(cast->execute(fn_ctx, cast_block, {0}, 2, 1).ok()); + const auto* result_const = + check_and_get_column(cast_block.get_by_position(2).column.get()); + ASSERT_NE(result_const, nullptr); + EXPECT_NE(check_and_get_column(&result_const->get_data_column()), nullptr); + + EXPECT_TRUE(curdate->close(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + EXPECT_TRUE(curdate->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); +} + +TEST(VTimestampFunctionsTest, utc_timestamp_legacy_datetime_result_for_old_plan) { + ColumnsWithTypeAndName arguments; + auto legacy_datetime_type = std::make_shared(); + auto utc_timestamp = SimpleFunctionFactory::instance().get_function("utc_timestamp", arguments, + legacy_datetime_type); + ASSERT_NE(utc_timestamp, nullptr); + + FunctionUtils fn_utils(legacy_datetime_type, {}, false); + auto* fn_ctx = fn_utils.get_fn_ctx(); + ASSERT_TRUE(utc_timestamp->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); + ASSERT_TRUE(utc_timestamp->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + + Block utc_timestamp_block {{nullptr, legacy_datetime_type, "utc_timestamp"}}; + ASSERT_TRUE(utc_timestamp->execute(fn_ctx, utc_timestamp_block, {}, 0, 1).ok()); + const auto* utc_timestamp_const = + check_and_get_column(utc_timestamp_block.get_by_position(0).column.get()); + ASSERT_NE(utc_timestamp_const, nullptr); + ASSERT_NE(check_and_get_column(&utc_timestamp_const->get_data_column()), + nullptr); + + auto datetimev2_type = std::make_shared(0); + ColumnsWithTypeAndName cast_arguments { + {utc_timestamp_block.get_by_position(0).column, legacy_datetime_type, "utc_timestamp"}, + {nullptr, datetimev2_type, "target_type"}}; + auto cast = + SimpleFunctionFactory::instance().get_function("CAST", cast_arguments, datetimev2_type); + ASSERT_NE(cast, nullptr); + Block cast_block {cast_arguments}; + cast_block.insert({nullptr, datetimev2_type, "result"}); + ASSERT_TRUE(cast->execute(fn_ctx, cast_block, {0}, 2, 1).ok()); + const auto* result_const = + check_and_get_column(cast_block.get_by_position(2).column.get()); + ASSERT_NE(result_const, nullptr); + EXPECT_NE(check_and_get_column(&result_const->get_data_column()), nullptr); + + EXPECT_TRUE(utc_timestamp->close(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + EXPECT_TRUE(utc_timestamp->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); +} + +TEST(VTimestampFunctionsTest, curtime_legacy_thrift_time_result_for_old_plan) { + // TIME used thrift enum value 18 before it was removed from the IDL. Build the type exactly as + // it arrives from a 3.1 FE and verify that the 4.0 BE normalizes it to TIMEV2. + TScalarType scalar_type; + scalar_type.__set_type(static_cast(18)); + TTypeNode type_node; + type_node.__set_type(TTypeNodeType::SCALAR); + type_node.__set_scalar_type(scalar_type); + TTypeDesc type_desc; + type_desc.types.push_back(type_node); + + auto legacy_time_type = DataTypeFactory::instance().create_data_type(type_desc); + ASSERT_NE(legacy_time_type, nullptr); + ASSERT_EQ(legacy_time_type->get_primitive_type(), TYPE_TIMEV2); + + ColumnsWithTypeAndName arguments; + auto curtime = + SimpleFunctionFactory::instance().get_function("curtime", arguments, legacy_time_type); + ASSERT_NE(curtime, nullptr); + + FunctionUtils fn_utils(legacy_time_type, {}, false); + auto* fn_ctx = fn_utils.get_fn_ctx(); + ASSERT_TRUE(curtime->open(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); + ASSERT_TRUE(curtime->open(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + + Block block {{nullptr, legacy_time_type, "curtime"}}; + ASSERT_TRUE(curtime->execute(fn_ctx, block, {}, 0, 1).ok()); + const auto* result_const = + check_and_get_column(block.get_by_position(0).column.get()); + ASSERT_NE(result_const, nullptr); + EXPECT_NE(check_and_get_column(&result_const->get_data_column()), nullptr); + + EXPECT_TRUE(curtime->close(fn_ctx, FunctionContext::THREAD_LOCAL).ok()); + EXPECT_TRUE(curtime->close(fn_ctx, FunctionContext::FRAGMENT_LOCAL).ok()); +} + } // namespace doris::vectorized