// Copyright 2023 PingCAP, Inc. // // 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace DB { namespace tests { template struct Nullable { using NativeType = T; }; template struct TypeTraits { static constexpr bool is_nullable = false; static constexpr bool is_decimal = false; using FieldType = typename NearestFieldType::Type; }; template <> struct TypeTraits { static constexpr bool is_nullable = false; static constexpr bool is_decimal = false; using FieldType = Null; }; template <> struct TypeTraits { static constexpr bool is_nullable = false; static constexpr bool is_decimal = false; using FieldType = DataTypeMyDate::FieldType; }; template <> struct TypeTraits { static constexpr bool is_nullable = false; static constexpr bool is_decimal = false; using FieldType = DataTypeMyDateTime::FieldType; }; template <> struct TypeTraits { static constexpr bool is_nullable = false; static constexpr bool is_decimal = false; using FieldType = DataTypeMyDuration::FieldType; }; template struct TypeTraits> { static constexpr bool is_nullable = true; static constexpr bool is_decimal = false; using FieldType = std::optional::FieldType>; }; template struct TypeTraits> { static constexpr bool is_nullable = false; static constexpr bool is_decimal = true; using DecimalType = Decimal; using FieldType = DecimalField; }; template struct TypeTraits>> { static constexpr bool is_nullable = true; static constexpr bool is_decimal = true; using DecimalType = Decimal; using FieldType = std::optional>; }; template struct InferredDataType; template <> struct InferredDataType { using Type = DataTypeNothing; }; template <> struct InferredDataType { using Type = DataTypeUInt8; }; template <> struct InferredDataType { using Type = DataTypeUInt16; }; template <> struct InferredDataType { using Type = DataTypeUInt32; }; template <> struct InferredDataType { using Type = DataTypeUInt64; }; template <> struct InferredDataType { using Type = DataTypeInt8; }; template <> struct InferredDataType { using Type = DataTypeInt16; }; template <> struct InferredDataType { using Type = DataTypeInt32; }; template <> struct InferredDataType { using Type = DataTypeInt64; }; template <> struct InferredDataType { using Type = DataTypeFloat32; }; template <> struct InferredDataType { using Type = DataTypeFloat64; }; template <> struct InferredDataType { using Type = DataTypeString; }; template <> struct InferredDataType { using Type = DataTypeMyDate; }; template <> struct InferredDataType { using Type = DataTypeMyDateTime; }; template <> struct InferredDataType { using Type = DataTypeMyDuration; }; template struct InferredDataType> { using Type = DataTypeDecimal>; }; template <> struct InferredDataType { using Type = DataTypeArray; }; template using InferredFieldType = typename TypeTraits::FieldType; template using InferredDataVector = std::vector>; template using InferredDataInitializerList = std::initializer_list>; template using InferredLiteralType = std::conditional_t::is_nullable, std::optional, String>; template using InferredLiteralVector = std::vector>; template using InferredLiteralInitializerList = std::initializer_list>; template DataTypePtr makeDataType(const Args &... args) { if constexpr (TypeTraits::is_nullable) return makeNullable(makeDataType(args...)); else return std::make_shared::Type>(args...); } template Field makeField(const T & value) { return Field(value); } template Field makeField(const std::optional & value) { if (value.has_value()) return Field(value.value()); else return Null(); } template ColumnPtr makeColumn(const DataTypePtr & data_type, const InferredDataVector & vec) { auto column = data_type->createColumn(); for (const auto & data : vec) column->insert(makeField(data)); return column; } template ColumnPtr makeConstColumn(const DataTypePtr & data_type, size_t size, const InferredFieldType & value) { return data_type->createColumnConst(size, makeField(value)); } ColumnWithTypeAndName createOnlyNullColumnConst(size_t size, const String & name = ""); ColumnWithTypeAndName createOnlyNullColumn(size_t size, const String & name = ""); template ColumnWithTypeAndName createColumn(const InferredDataVector & vec, const String & name = "", Int64 column_id = 0) { DataTypePtr data_type = makeDataType(); return {makeColumn(data_type, vec), data_type, name, column_id}; } template ColumnWithTypeAndName createVecFloat32Column( const InferredDataVector & vec, const String & name = "", Int64 column_id = 0) { DataTypePtr data_type = std::make_shared(typeFromString("Float32")); return {makeColumn(data_type, vec), data_type, name, column_id}; } template ColumnWithTypeAndName createColumn(InferredDataInitializerList init, const String & name = "") { auto vec = InferredDataVector(init); return createColumn(vec, name); } template ColumnWithTypeAndName createConstColumn(size_t size, const InferredFieldType & value, const String & name = "") { DataTypePtr data_type = makeDataType(); return {makeConstColumn(data_type, size, value), data_type, name}; } template ColumnWithTypeAndName createColumn( const std::tuple & data_type_args, const InferredDataVector & vec, const String & name = "") { DataTypePtr data_type = std::apply(makeDataType, data_type_args); return {makeColumn(data_type, vec), data_type, name}; } template ColumnWithTypeAndName createColumn( const std::tuple & data_type_args, InferredDataInitializerList init, const String & name = "") { auto vec = InferredDataVector(init); return createColumn(data_type_args, vec, name); } template ColumnWithTypeAndName createConstColumn( const std::tuple & data_type_args, size_t size, const InferredFieldType & value, const String & name = "") { DataTypePtr data_type = std::apply(makeDataType, data_type_args); return {makeConstColumn(data_type, size, value), data_type, name}; } template ColumnWithTypeAndName createDateTimeColumn(std::initializer_list> init, int fraction) { DataTypePtr data_type_ptr = std::make_shared(fraction); if constexpr (is_nullable) { data_type_ptr = makeNullable(data_type_ptr); } auto col = data_type_ptr->createColumn(); for (const auto & dt : init) { if (dt.has_value()) col->insert(Field(dt->toPackedUInt())); else { if constexpr (is_nullable) { col->insert(Null()); } else { throw Exception("Null value for not nullable DataTypeMyDateTime"); } } } return {std::move(col), data_type_ptr, "datetime"}; } template ColumnWithTypeAndName createDateTimeColumnConst(size_t size, const std::optional & dt, int fraction) { DataTypePtr data_type_ptr = std::make_shared(fraction); if constexpr (is_nullable) { data_type_ptr = makeNullable(data_type_ptr); } ColumnPtr col; if (dt.has_value()) col = data_type_ptr->createColumnConst(size, Field(dt->toPackedUInt())); else { if constexpr (is_nullable) { col = data_type_ptr->createColumnConst(size, Field(Null())); } else { throw Exception("Null value for not nullable DataTypeMyDateTime"); } } return {std::move(col), data_type_ptr, "datetime"}; } template ColumnWithTypeAndName createDurationColumn(std::initializer_list> init, int fraction) { DataTypePtr data_type_ptr = std::make_shared(fraction); if constexpr (is_nullable) { data_type_ptr = makeNullable(data_type_ptr); } auto col = data_type_ptr->createColumn(); for (const auto & dt : init) { if (dt.has_value()) col->insert(Field(dt->nanoSecond())); else { if constexpr (is_nullable) { col->insert(Null()); } else { throw Exception("Null value for not nullable DataTypeMyDuration"); } } } return {std::move(col), data_type_ptr, "duration"}; } template ColumnWithTypeAndName createDurationColumnConst(size_t size, const std::optional & duration, int fraction) { DataTypePtr data_type_ptr = std::make_shared(fraction); if constexpr (is_nullable) { data_type_ptr = makeNullable(data_type_ptr); } ColumnPtr col; if (duration.has_value()) col = data_type_ptr->createColumnConst(size, Field(duration->nanoSecond())); else { if constexpr (is_nullable) { col = data_type_ptr->createColumnConst(size, Field(Null())); } else { throw Exception("Null value for not nullable DataTypeMyDuration"); } } return {std::move(col), data_type_ptr, "duration"}; } // parse a string into decimal field. template typename TypeTraits::FieldType parseDecimal( const InferredLiteralType & literal_, PrecType max_prec, ScaleType expected_scale) { using Traits = TypeTraits; using DecimalType = typename Traits::DecimalType; using NativeType = typename DecimalType::NativeType; static_assert(is_signed_v); if constexpr (Traits::is_nullable) { if (!literal_.has_value()) return std::nullopt; } const String & literal = [&] { if constexpr (Traits::is_nullable) { RUNTIME_ASSERT(literal_.has_value()); return literal_.value(); } else return literal_; }(); auto parsed = DB::parseDecimal(literal.data(), literal.size()); if (!parsed.has_value()) throw TiFlashTestException(fmt::format("Failed to parse '{}'", literal)); auto [parsed_value, prec, scale] = parsed.value(); (void)prec; max_prec = std::min(max_prec, maxDecimalPrecision()); if (scale != expected_scale) { Int256 scale_factor = 1; size_t scale_diff = expected_scale > scale ? expected_scale - scale : scale - expected_scale; for (size_t i = 0; i < scale_diff; i++) scale_factor *= 10; if (expected_scale > scale) { parsed_value *= scale_factor; } else { bool need_round = (parsed_value >= 0 ? parsed_value : -parsed_value) % scale_factor >= scale_factor / 2; parsed_value /= scale_factor; if (need_round) { if (parsed_value > 0) parsed_value++; else parsed_value--; } } } auto max_value = DecimalMaxValue::get(max_prec); if (parsed_value > max_value || parsed_value < -max_value) throw TiFlashTestException( fmt::format("Input {} overflow for decimal({},{})", literal, max_prec, expected_scale)); auto value = static_cast(parsed_value); return DecimalField(value, expected_scale); } // e.g. `createColumn(std::make_tuple(9, 4), {"99999.9999"})` template ColumnWithTypeAndName createColumn( const std::tuple & data_type_args, const InferredLiteralVector & literals, const String & name = "", std::enable_if_t::is_decimal, int> = 0) { DataTypePtr data_type = std::apply(makeDataType, data_type_args); PrecType prec = getDecimalPrecision(*removeNullable(data_type), 0); ScaleType scale = getDecimalScale(*removeNullable(data_type), 0); InferredDataVector vec; vec.reserve(literals.size()); for (const auto & literal : literals) vec.push_back(parseDecimal(literal, prec, scale)); return {makeColumn(data_type, vec), data_type, name}; } template ColumnWithTypeAndName createColumn( const std::tuple & data_type_args, InferredLiteralInitializerList literals, const String & name = "", std::enable_if_t::is_decimal, int> = 0) { auto vec = InferredLiteralVector(literals); return createColumn(data_type_args, vec, name); } // e.g. `createConstColumn(std::make_tuple(9, 4), 1, "99999.9999")` template ColumnWithTypeAndName createConstColumn( const std::tuple & data_type_args, size_t size, const InferredLiteralType & literal, const String & name = "", std::enable_if_t::is_decimal, int> = 0) { DataTypePtr data_type = std::apply(makeDataType, data_type_args); PrecType prec = getDecimalPrecision(*removeNullable(data_type), 0); ScaleType scale = getDecimalScale(*removeNullable(data_type), 0); return {makeConstColumn(data_type, size, parseDecimal(literal, prec, scale)), data_type, name}; } // resolve ambiguous overloads for `createColumn>(..., {std::nullopt})`. template ColumnWithTypeAndName createColumn( const std::tuple & data_type_args, std::initializer_list init, const String & name = "", std::enable_if_t::is_nullable, int> = 0) { InferredDataVector vec(init.size(), std::nullopt); return createColumn(data_type_args, vec, name); } // resolve ambiguous overloads for `createConstColumn>(..., std::nullopt)`. template ColumnWithTypeAndName createConstColumn( const std::tuple & data_type_args, size_t size, std::nullopt_t, const String & name = "", std::enable_if_t::is_nullable, int> = 0) { return createConstColumn(data_type_args, size, InferredFieldType(std::nullopt), name); } String getColumnsContent(const ColumnsWithTypeAndName & cols); /// We can designate the range of columns printed with begin and end. range: [begin, end) String getColumnsContent(const ColumnsWithTypeAndName & cols, size_t begin, size_t end); // This wrapper function only serves to construct columns input for function-like macros, // since preprocessor recognizes `{col1, col2, col3}` as three arguments instead of one. // E.g. preprocessor does not allow us to write `ASSERT_COLUMNS_EQ_R({col1, col2, col3}, actual_cols)`, // but with this func we can write `ASSERT_COLUMNS_EQ_R(createColumns{col1, col2, col3}, actual_cols)` instead. ColumnsWithTypeAndName createColumns(const ColumnsWithTypeAndName & cols); ::testing::AssertionResult dataTypeEqual(const DataTypePtr & expected, const DataTypePtr & actual); ::testing::AssertionResult columnEqual( const ColumnPtr & expected, const ColumnPtr & actual, const TiDB::ITiDBCollator * collator = nullptr, bool is_floating_point = false, bool exact_match_for_floating_point = false); // ignore name ::testing::AssertionResult columnEqual( const ColumnWithTypeAndName & expected, const ColumnWithTypeAndName & actual, const TiDB::ITiDBCollator * collator = nullptr, bool exact_match_for_floating_point = false); ::testing::AssertionResult blockEqual(const Block & expected, const Block & actual); ::testing::AssertionResult columnsEqual( const ColumnsWithTypeAndName & expected, const ColumnsWithTypeAndName & actual, bool _restrict); /// Note that during execution, the offsets of each column might be reordered. ColumnWithTypeAndName executeFunction( Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, const String & val = "", bool raw_function_test = false); ColumnWithTypeAndName executeFunction( Context & context, const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, const String & val = "", bool raw_function_test = false); template ColumnWithTypeAndName executeFunction( Context & context, const String & func_name, const ColumnWithTypeAndName & first_column, const Args &... columns) { ColumnsWithTypeAndName vec({first_column, columns...}); return executeFunction(context, func_name, vec); } DataTypePtr getReturnTypeForFunction( Context & context, const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, bool raw_function_test = false); template ColumnWithTypeAndName createNullableColumn( InferredDataVector init_vec, const std::vector & null_map, const String name = "", Int64 column_id = 0) { static_assert(TypeTraits::is_nullable == false); auto updated_vec = InferredDataVector>(); size_t vec_length = std::min(init_vec.size(), null_map.size()); for (size_t i = 0; i < vec_length; i++) { if (null_map[i]) updated_vec.push_back({}); else updated_vec.push_back(init_vec[i]); } return createColumn>(updated_vec, name, column_id); } template ColumnWithTypeAndName createNullableColumn( InferredDataInitializerList init, const std::vector & null_map, const String name = "", Int64 column_id = 0) { static_assert(TypeTraits::is_nullable == false); auto vec = InferredDataVector(init); return createNullableColumn(vec, null_map, name, column_id); } template ColumnWithTypeAndName createNullableColumn( const std::tuple & data_type_args, const InferredDataVector & init_vec, const std::vector & null_map, const String & name = "") { static_assert(TypeTraits::is_nullable == false); auto updated_vec = InferredDataVector>(); size_t vec_length = std::min(init_vec.size(), null_map.size()); for (size_t i = 0; i < vec_length; i++) { if (null_map[i]) updated_vec.push_back({}); else updated_vec.push_back(init_vec[i]); } return createColumn>(data_type_args, updated_vec, name); } template ColumnWithTypeAndName createNullableColumn( const std::tuple & data_type_args, InferredDataInitializerList init, const std::vector & null_map, const String & name = "") { static_assert(TypeTraits::is_nullable == false); auto vec = InferredDataVector(init); return createNullableColumn(data_type_args, vec, null_map, name); } template ColumnWithTypeAndName createNullableColumn( const std::tuple & data_type_args, const InferredLiteralVector & init_vec, const std::vector & null_map, const String & name = "", std::enable_if_t::is_decimal, int> = 0) { static_assert(TypeTraits::is_nullable == false); auto updated_vec = InferredLiteralVector>(); size_t vec_length = std::min(init_vec.size(), null_map.size()); for (size_t i = 0; i < vec_length; i++) { if (null_map[i]) updated_vec.push_back({}); else updated_vec.push_back(init_vec[i]); } return createColumn>(data_type_args, updated_vec, name, 0); } template ColumnWithTypeAndName createNullableColumn( const std::tuple & data_type_args, const InferredLiteralInitializerList & init, const std::vector & null_map, const String & name = "", std::enable_if_t::is_decimal, int> = 0) { static_assert(TypeTraits::is_nullable == false); auto vec = InferredLiteralVector(init); return createNullableColumn(data_type_args, vec, null_map, name, 0); } template ColumnWithTypeAndName toNullableVec(const std::vector::FieldType>> & v) { return createColumn>(v); } template ColumnWithTypeAndName toVec(const std::vector::FieldType> & v) { return createColumn(v); } template ColumnWithTypeAndName toNullableVec( String name, const std::vector::FieldType>> & v) { return createColumn>(v, name); } template ColumnWithTypeAndName toVec(String name, const std::vector::FieldType> & v) { return createColumn(v, name); } ColumnWithTypeAndName toDatetimeVec(String name, const std::vector & v, int fsp); ColumnWithTypeAndName toNullableDatetimeVec(String name, const std::vector & v, int fsp); struct FuncMetaData { String val; // This is for the val field of tipb::expr }; class FunctionTest : public ::testing::Test { protected: void SetUp() override { initializeDAGContext(); } public: static void SetUpTestCase() { try { DB::registerFunctions(); } catch (DB::Exception &) { // Maybe another test has already registered, ignore exception here. } } FunctionTest(); virtual void initializeDAGContext(); ColumnWithTypeAndName executeFunction( const String & func_name, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, bool raw_function_test = false); template ColumnWithTypeAndName executeFunction( const String & func_name, const ColumnWithTypeAndName & first_column, const Args &... columns) { ColumnsWithTypeAndName vec({first_column, columns...}); return executeFunction(func_name, vec); } ColumnWithTypeAndName executeFunction( const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns, const TiDB::TiDBCollatorPtr & collator = nullptr, bool raw_function_test = false); template ColumnWithTypeAndName executeFunction( const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnWithTypeAndName & first_column, const Args &... columns) { ColumnsWithTypeAndName vec({first_column, columns...}); return executeFunction(func_name, argument_column_numbers, vec); } ColumnWithTypeAndName executeFunctionWithMetaData( const String & func_name, const ColumnsWithTypeAndName & columns, const FuncMetaData & meta, const TiDB::TiDBCollatorPtr & collator = nullptr); ColumnWithTypeAndName executeFunctionWithMetaData( const String & func_name, const ColumnNumbers & argument_column_numbers, const ColumnsWithTypeAndName & columns, const FuncMetaData & meta, const TiDB::TiDBCollatorPtr & collator = nullptr); ColumnWithTypeAndName executeCastJsonAsStringFunction( const ColumnWithTypeAndName & input_column, const tipb::FieldType & field_type); DAGContext & getDAGContext() { RUNTIME_ASSERT(dag_context_ptr != nullptr); return *dag_context_ptr; } protected: ContextPtr context; std::unique_ptr dag_context_ptr; }; template struct TestTypeList { }; using TestNullableIntTypes = TestTypeList, Nullable, Nullable, Nullable>; using TestNullableUIntTypes = TestTypeList, Nullable, Nullable, Nullable>; using TestIntTypes = TestTypeList; using TestUIntTypes = TestTypeList; using TestAllIntTypes = TestTypeList, Nullable, Nullable, Nullable, Int8, Int16, Int32, Int64>; using TestAllUIntTypes = TestTypeList< Nullable, Nullable, Nullable, Nullable, UInt8, UInt16, UInt32, UInt64>; template class Func, typename FuncParam> struct TestTypeSingle; template class Func, typename FuncParam> struct TestTypeSingle, Func, FuncParam> { static void run(FuncParam & p) { Func::operator()(p); // Recursively handle the rest of T2List TestTypeSingle, Func, FuncParam>::run(p); } }; template class Func, typename FuncParam> struct TestTypeSingle, Func, FuncParam> { static void run(FuncParam &) { // Do nothing when T2List is empty } }; template class Func, typename FuncParam> struct TestTypePair; template < typename T1, typename... T1Rest, typename T2List, template class Func, typename FuncParam> struct TestTypePair, T2List, Func, FuncParam> { static void run(FuncParam & p) { // For the current T1, traverse all types in T2List TestTypeSingle::run(p); // Recursively handle the rest of T1List TestTypePair, T2List, Func, FuncParam>::run(p); } }; template class Func, typename FuncParam> struct TestTypePair, T2List, Func, FuncParam> { static void run(FuncParam &) { // Do nothing when T1List is empty } }; #define ASSERT_COLUMN_EQ(expected, actual) ASSERT_TRUE(DB::tests::columnEqual((expected), (actual))) /// ASSERT_COLUMN_EQ_V2 compares floating point using exact match algorithm #define ASSERT_COLUMN_EQ_V2(expected, actual) ASSERT_TRUE(DB::tests::columnEqual((expected), (actual), nullptr, true)) #define ASSERT_BLOCK_EQ(expected, actual) ASSERT_TRUE(DB::tests::blockEqual((expected), (actual))) /// restrictly checking columns equality, both data set and each row's offset should be the same #define ASSERT_COLUMNS_EQ_R(expected, actual) ASSERT_TRUE(DB::tests::columnsEqual((expected), (actual), true)) /// unrestrictly checking columns equality, only checking data set equality #define ASSERT_COLUMNS_EQ_UR(expected, actual) ASSERT_TRUE(DB::tests::columnsEqual((expected), (actual), false)) /// Check the profile event change after the body. #define ASSERT_PROFILE_EVENT(event, diff_expr, ...) \ do \ { \ auto profile_event_count = ProfileEvents::get(event); \ {__VA_ARGS__}; \ ASSERT_EQ(profile_event_count diff_expr, ProfileEvents::get(event)); \ } while (false); } // namespace tests } // namespace DB