This is an automated email from the ASF dual-hosted git repository. kxiao pushed a commit to branch branch-2.0 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.0 by this push: new 214ee1b712f [fix](eq_for_null) fix incorrect logic in function eq_for_null #36004 #36164 (#36123) 214ee1b712f is described below commit 214ee1b712f42ba12a17a635602c21f2871d784d Author: zhiqiang <seuhezhiqi...@163.com> AuthorDate: Thu Jun 20 10:59:38 2024 +0800 [fix](eq_for_null) fix incorrect logic in function eq_for_null #36004 #36164 (#36123) --- be/src/vec/functions/comparison_equal_for_null.cpp | 139 +++-- be/src/vec/functions/function.cpp | 2 +- be/test/vec/function/function_eq_for_null_test.cpp | 643 +++++++++++++++++++++ 3 files changed, 744 insertions(+), 40 deletions(-) diff --git a/be/src/vec/functions/comparison_equal_for_null.cpp b/be/src/vec/functions/comparison_equal_for_null.cpp index 32786310653..7644e88fd56 100644 --- a/be/src/vec/functions/comparison_equal_for_null.cpp +++ b/be/src/vec/functions/comparison_equal_for_null.cpp @@ -29,6 +29,7 @@ #include "vec/columns/column_const.h" #include "vec/columns/column_nullable.h" #include "vec/columns/column_vector.h" +#include "vec/columns/columns_number.h" #include "vec/common/assert_cast.h" #include "vec/core/block.h" #include "vec/core/column_numbers.h" @@ -38,6 +39,7 @@ #include "vec/data_types/data_type_nullable.h" #include "vec/data_types/data_type_number.h" #include "vec/functions/function.h" +#include "vec/functions/function_helpers.h" #include "vec/functions/simple_function_factory.h" namespace doris { @@ -66,69 +68,119 @@ public: size_t result, size_t input_rows_count) override { ColumnWithTypeAndName& col_left = block.get_by_position(arguments[0]); ColumnWithTypeAndName& col_right = block.get_by_position(arguments[1]); + + const bool left_const = is_column_const(*col_left.column); + const bool right_const = is_column_const(*col_right.column); bool left_only_null = col_left.column->only_null(); bool right_only_null = col_right.column->only_null(); + if (left_only_null && right_only_null) { + // TODO: return ColumnConst after function.cpp::default_implementation_for_constant_arguments supports it. auto result_column = ColumnVector<UInt8>::create(input_rows_count, 1); block.get_by_position(result).column = std::move(result_column); return Status::OK(); } else if (left_only_null) { auto right_type_nullable = col_right.type->is_nullable(); if (!right_type_nullable) { + // right_column is not nullable, so result is all false. block.get_by_position(result).column = ColumnVector<UInt8>::create(input_rows_count, 0); } else { - auto const* nullable_right_col = - assert_cast<const ColumnNullable*>(col_right.column.get()); - block.get_by_position(result).column = - nullable_right_col->get_null_map_column().clone_resized(input_rows_count); + // right_column is nullable + const ColumnNullable* nullable_right_col = nullptr; + if (right_const) { + nullable_right_col = assert_cast<const ColumnNullable*>( + &(assert_cast<const ColumnConst*>(col_right.column.get()) + ->get_data_column())); + // Actually, when we reach here, the result can only be all false (all not null). + // Since if right column is const, and it is all null, we will be short-circuited + // to (left_only_null && right_only_null) branch. So here the right column is all not null. + block.get_by_position(result).column = ColumnUInt8::create( + input_rows_count, + nullable_right_col->get_null_map_column().get_data()[0]); + } else { + nullable_right_col = assert_cast<const ColumnNullable*>(col_right.column.get()); + // left column is all null, so result has same nullmap with right column. + block.get_by_position(result).column = + nullable_right_col->get_null_map_column().clone(); + } } return Status::OK(); } else if (right_only_null) { auto left_type_nullable = col_left.type->is_nullable(); if (!left_type_nullable) { + // right column is all but left column is not nullable, so result is all false. block.get_by_position(result).column = ColumnVector<UInt8>::create(input_rows_count, (UInt8)0); } else { - auto const* nullable_left_col = - assert_cast<const ColumnNullable*>(col_left.column.get()); - block.get_by_position(result).column = - nullable_left_col->get_null_map_column().clone_resized(input_rows_count); + const ColumnNullable* nullable_left_col = nullptr; + if (left_const) { + nullable_left_col = assert_cast<const ColumnNullable*>( + &(assert_cast<const ColumnConst*>(col_left.column.get()) + ->get_data_column())); + block.get_by_position(result).column = ColumnUInt8::create( + input_rows_count, + nullable_left_col->get_null_map_column().get_data()[0]); + } else { + nullable_left_col = assert_cast<const ColumnNullable*>(col_left.column.get()); + // right column is all null, so result has same nullmap with left column. + block.get_by_position(result).column = + nullable_left_col->get_null_map_column().clone(); + } } return Status::OK(); } - const auto& [left_col, left_const] = unpack_if_const(col_left.column); - const auto& [right_col, right_const] = unpack_if_const(col_right.column); - const auto left_column = check_and_get_column<ColumnNullable>(left_col); - const auto right_column = check_and_get_column<ColumnNullable>(right_col); + const ColumnNullable* left_column = nullptr; + const ColumnNullable* right_column = nullptr; + + if (left_const) { + left_column = check_and_get_column<const ColumnNullable>( + assert_cast<const ColumnConst*>(col_left.column.get())->get_data_column_ptr()); + } else { + left_column = check_and_get_column<const ColumnNullable>(col_left.column); + } + + if (right_const) { + right_column = check_and_get_column<const ColumnNullable>( + assert_cast<const ColumnConst*>(col_right.column.get())->get_data_column_ptr()); + } else { + right_column = check_and_get_column<const ColumnNullable>(col_right.column); + } bool left_nullable = left_column != nullptr; bool right_nullable = right_column != nullptr; if (left_nullable == right_nullable) { auto return_type = std::make_shared<DataTypeUInt8>(); - + auto left_column_tmp = + left_nullable ? left_column->get_nested_column_ptr() : col_left.column; + auto right_column_tmp = + right_nullable ? right_column->get_nested_column_ptr() : col_right.column; ColumnsWithTypeAndName eq_columns { ColumnWithTypeAndName { - left_nullable ? left_column->get_nested_column_ptr() : col_left.column, + left_const ? ColumnConst::create(left_column_tmp, input_rows_count) + : left_column_tmp, left_nullable ? assert_cast<const DataTypeNullable*>(col_left.type.get()) ->get_nested_type() : col_left.type, ""}, - ColumnWithTypeAndName {left_nullable ? right_column->get_nested_column_ptr() - : col_right.column, - left_nullable ? assert_cast<const DataTypeNullable*>( - col_right.type.get()) - ->get_nested_type() - : col_right.type, - ""}}; + ColumnWithTypeAndName { + right_const ? ColumnConst::create(right_column_tmp, input_rows_count) + : right_column_tmp, + left_nullable + ? assert_cast<const DataTypeNullable*>(col_right.type.get()) + ->get_nested_type() + : col_right.type, + ""}}; Block temporary_block(eq_columns); auto func_eq = SimpleFunctionFactory::instance().get_function("eq", eq_columns, return_type); - DCHECK(func_eq); + DCHECK(func_eq) << fmt::format("Left type {} right type {} return type {}", + col_left.type->get_name(), col_right.type->get_name(), + return_type->get_name()); temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); RETURN_IF_ERROR( func_eq->execute(context, temporary_block, {0, 1}, 2, input_rows_count)); @@ -147,7 +199,17 @@ public: } block.get_by_position(result).column = temporary_block.get_by_position(2).column; - } else { //left_nullable != right_nullable + } else { + // left_nullable != right_nullable + // If we go here, the DataType of left and right column is different. + // So there will be EXACTLLY one Column has Nullable data type. + // Possible cases: + // 1. left Datatype is nullable, right column is not nullable. + // 2. left Datatype is not nullable, right column is nullable. + + // Why make_nullable here? + // Because function eq uses default implementation for null, + // and we have nullable arguments, so the return type should be nullable. auto return_type = make_nullable(std::make_shared<DataTypeUInt8>()); const ColumnsWithTypeAndName eq_columns { @@ -165,13 +227,24 @@ public: auto res_nullable_column = assert_cast<ColumnNullable*>( std::move(*temporary_block.get_by_position(2).column).mutate().get()); auto& null_map = res_nullable_column->get_null_map_data(); - auto& res_map = + auto& res_nested_col = assert_cast<ColumnVector<UInt8>&>(res_nullable_column->get_nested_column()) .get_data(); - auto* __restrict res = res_map.data(); - auto* __restrict l = null_map.data(); - _exec_nullable_inequal(res, l, input_rows_count, left_const); + // Input of eq_for_null: + // Left: [1, 1, 1, 1](ColumnConst(ColumnInt32)) + // Right: [1, 1, 1, 1] & [0, 1, 0, 1] (ColumnNullable(ColumnInt32)) + // Above input will be passed to function eq, and result will be + // Result: [1, x, 1, x] & [0, 1, 0, 1] (ColumnNullable(ColumnUInt8)), x means default value. + // The expceted result of eq_for_null is: + // Except: [1, 0, 1, 0] (ColumnUInt8) + // We already have assumption that there is only one nullable column in input. + // So if one row of res_nullable_column is null, the result row of eq_for_null should be 0. + // For others, the result will be same with function eq. + + for (int i = 0; i < input_rows_count; ++i) { + res_nested_col[i] &= (null_map[i] != 1); + } block.get_by_position(result).column = res_nullable_column->get_nested_column_ptr(); } @@ -196,18 +269,6 @@ private: } } } - static void _exec_nullable_inequal(unsigned char* result, const unsigned char* left, - size_t rows, bool left_const) { - if (left_const) { - for (int i = 0; i < rows; ++i) { - result[i] &= (left[0] != 1); - } - } else { - for (int i = 0; i < rows; ++i) { - result[i] &= (left[i] != 1); - } - } - } }; void register_function_comparison_eq_for_null(SimpleFunctionFactory& factory) { diff --git a/be/src/vec/functions/function.cpp b/be/src/vec/functions/function.cpp index a4ed868bdfd..300193ac39e 100644 --- a/be/src/vec/functions/function.cpp +++ b/be/src/vec/functions/function.cpp @@ -210,7 +210,7 @@ Status PreparedFunctionImpl::default_implementation_for_constant_arguments( } else { result_column = temporary_block.get_by_position(arguments_size).column; } - + // We shuold handle the case where the result column is also a ColumnConst. block.get_by_position(result).column = ColumnConst::create(result_column, input_rows_count); *executed = true; return Status::OK(); diff --git a/be/test/vec/function/function_eq_for_null_test.cpp b/be/test/vec/function/function_eq_for_null_test.cpp new file mode 100644 index 00000000000..90b04b51608 --- /dev/null +++ b/be/test/vec/function/function_eq_for_null_test.cpp @@ -0,0 +1,643 @@ +// 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 <gtest/gtest.h> + +#include <cassert> +#include <cstddef> +#include <string> +#include <vector> + +#include "common/status.h" +#include "function_test_util.h" +#include "gtest/gtest_pred_impl.h" +#include "udf/udf.h" +#include "vec/columns/column_const.h" +#include "vec/columns/column_nullable.h" +#include "vec/columns/columns_number.h" +#include "vec/core/column_with_type_and_name.h" +#include "vec/core/types.h" +#include "vec/data_types/data_type_nullable.h" +#include "vec/data_types/data_type_string.h" +#include "vec/functions/function.h" +#include "vec/functions/function_helpers.h" + +namespace doris::vectorized { + +TEST(EqForNullFunctionTest, both_only_null) { + const size_t input_rows_count = 100; + + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), null_map_for_all_null->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), null_map_for_all_null->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 1); + } +} + +TEST(EqForNullFunctionTest, both_only_null_const) { + const size_t input_rows_count = 100; + + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + + ColumnWithTypeAndName left { + ColumnConst::create(ColumnNullable::create(left_i32->clone_resized(1), + null_map_for_all_null->clone_resized(1)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnConst::create(ColumnNullable::create(right_i32->clone_resized(1), + null_map_for_all_null->clone_resized(1)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto col_holder = temporary_block.get_by_position(2).column->convert_to_full_column_if_const(); + auto result_column = assert_cast<const ColumnUInt8*>(col_holder.get()); + + std::cout << "Output rows count " << result_column->size() << std::endl; + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 1); + } +} + +TEST(EqForNullFunctionTest, left_only_null_right_const) { + const size_t input_rows_count = 100; + // Input [NULL, NULL, NULL, NULL] & [1, 1, 1, 1] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), ColumnUInt8::create(input_rows_count, 1)), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + std::cout << "left only null " << left.column->only_null() << std::endl; + ColumnWithTypeAndName right { + ColumnConst::create( + ColumnNullable::create(right_i32->clone_resized(1), ColumnUInt8::create(1, 0)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + std::cout << "Output rows count " << result_column->size() << std::endl; + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 0) + << fmt::format("Data {}, i {}", result_column->get_data()[i], i); + } +} + +TEST(EqForNullFunctionTest, left_const_right_only_null) { + const size_t input_rows_count = 100; + + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + + ColumnWithTypeAndName left { + ColumnConst::create( + ColumnNullable::create(left_i32->clone_resized(1), ColumnUInt8::create(1, 0)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), ColumnUInt8::create(input_rows_count, 1)), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + std::cout << "Output rows count " << result_column->size() << std::endl; + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 0) + << fmt::format("Data {}, i {}", result_column->get_data()[i], i); + } +} + +TEST(EqForNullFunctionTest, left_only_null_right_nullable) { + const size_t input_rows_count = 100; + + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), ColumnUInt8::create(input_rows_count, 1)), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 0); + } else { + ASSERT_EQ(result_column->get_data()[i], 1); + } + } +} + +TEST(EqForNullFunctionTest, left_only_null_right_not_nullable) { + const size_t input_rows_count = 100; + // left [NULL, NULL, NULL, NULL] & right [1, 1, 1, 1] + // result [0, 0, 0, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), ColumnUInt8::create(input_rows_count, 1)), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right {right_i32->clone(), std::make_shared<DataTypeInt32>(), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 0); + } +} + +TEST(EqForNullFunctionTest, left_nullable_right_only_null) { + const size_t input_rows_count = 100; + // LEFT: [1, NULL, 1, NULL] & RIGHT: [NULL, NULL, NULL, NULL] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), null_map_for_all_null->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 0); + } else { + ASSERT_EQ(result_column->get_data()[i], 1); + } + } +} + +TEST(EqForNullFunctionTest, left_not_nullable_right_only_null) { + const size_t input_rows_count = 100; + // LEFT: [1, 1, 1, 1] & RIGHT: [NULL, NULL, NULL, NULL] + // output [0, 0, 0, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto null_map_for_all_null = ColumnUInt8::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left {left_i32->clone(), std::make_shared<DataTypeInt32>(), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), null_map_for_all_null->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 0); + } +} + +TEST(EqForNullFunctionTest, left_nullable_right_nullable) { + const size_t input_rows_count = 100; + // input: [NULL, 1, NULL, 1] & [NULL, 1, NULL, 1] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + ASSERT_EQ(result_column->get_data()[i], 1); + } +} + +TEST(EqForNullFunctionTest, left_nullable_right_not_nullable) { + const size_t input_rows_count = 100; + // input [1, NULL, 1, NULL] & [1, 1, 1, 1] + // output [1, 0, 1, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right {right_i32->clone(), std::make_shared<DataTypeInt32>(), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 1); + } else { + ASSERT_EQ(result_column->get_data()[i], 0); + } + } +} + +TEST(EqForNullFunctionTest, left_not_nullable_right_nullable) { + const size_t input_rows_count = 100; + // input [1, 1, 1, 1] & [1, NULL, 1, NULL] + // output [1, 0, 1, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left {left_i32->clone(), std::make_shared<DataTypeInt32>(), "right"}; + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 1); + } else { + ASSERT_EQ(result_column->get_data()[i], 0); + } + } +} + +TEST(EqForNullFunctionTest, left_const_not_nullable_right_nullable) { + const size_t input_rows_count = 10; + // input [1, 1, 1, 1] & [1, NULL, 1, NULL] + // output [1, 0, 1, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left {ColumnConst::create(left_i32->clone_resized(1), input_rows_count), + std::make_shared<DataTypeInt32>(), "right"}; + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + EXPECT_EQ(result_column->get_data()[i], 1) + << fmt::format("i {} value {}", i, result_column->get_data()[i]); + } else { + EXPECT_EQ(result_column->get_data()[i], 0) + << fmt::format("i {} value {}", i, result_column->get_data()[i]); + } + } +} + +TEST(EqForNullFunctionTest, left_const_nullable_right_nullable) { + const size_t input_rows_count = 100; + // input [1, 1, 1, 1] & [1, NULL, 1, NULL] + // output [1, 0, 1, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnConst::create( + ColumnNullable::create(left_i32->clone_resized(1), ColumnUInt8::create(1, 0)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnNullable::create(right_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 1); + } else { + ASSERT_EQ(result_column->get_data()[i], 0); + } + } +} + +TEST(EqForNullFunctionTest, left_nullable_right_const_nullable) { + const size_t input_rows_count = 100; + // input [1, NULL, 1, NULL] & [1, 1, 1, 1] + // output [1, 0, 1, 0] + auto left_i32 = ColumnInt32::create(input_rows_count, 1); + auto right_i32 = ColumnInt32::create(input_rows_count, 1); + auto common_null_map = ColumnUInt8::create(); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + common_null_map->insert(0); + } else { + common_null_map->insert(1); + } + } + + ColumnWithTypeAndName left { + ColumnNullable::create(left_i32->clone(), common_null_map->clone()), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "left"}; + + ColumnWithTypeAndName right { + ColumnConst::create( + ColumnNullable::create(right_i32->clone_resized(1), ColumnUInt8::create(1, 0)), + input_rows_count), + std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), "right"}; + + auto return_type = std::make_shared<DataTypeUInt8>(); + auto func_eq_for_null = SimpleFunctionFactory::instance().get_function( + "eq_for_null", ColumnsWithTypeAndName {left, right}, return_type); + + Block temporary_block(ColumnsWithTypeAndName {left, right}); + temporary_block.insert(ColumnWithTypeAndName {nullptr, return_type, ""}); + FunctionContext* context = nullptr; + auto status = func_eq_for_null->execute(context, temporary_block, {0, 1}, 2, input_rows_count); + + ASSERT_TRUE(status.ok()); + + auto result_column = + assert_cast<const ColumnUInt8*>(temporary_block.get_by_position(2).column.get()); + + for (size_t i = 0; i < input_rows_count; ++i) { + if (i % 2 == 0) { + ASSERT_EQ(result_column->get_data()[i], 1); + } else { + ASSERT_EQ(result_column->get_data()[i], 0); + } + } +} + +} // namespace doris::vectorized \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org