diff --git a/be/src/vec/columns/column_execute_util.h b/be/src/vec/columns/column_execute_util.h new file mode 100644 index 00000000000000..fb4237a49d0e8e --- /dev/null +++ b/be/src/vec/columns/column_execute_util.h @@ -0,0 +1,349 @@ +// 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. + +#pragma once +#include +#include + +#include "runtime/define_primitive_type.h" +#include "runtime/primitive_type.h" +#include "vec/columns/column.h" +#include "vec/columns/column_const.h" +#include "vec/columns/column_nullable.h" +#include "vec/columns/column_vector.h" +#include "vec/core/column_with_type_and_name.h" +#include "vec/core/field.h" + +namespace doris::vectorized { + +// Utility tools for convenient column execution + +// ColumnElementView is used to distinguish between scalar columns and string columns +template +struct ColumnElementView { + using ColumnType = typename PrimitiveTypeTraits::ColumnType; + using ElementType = typename ColumnType::value_type; + const typename ColumnType::Container& data; + ElementType get_element(size_t idx) const { return data[idx]; } + + ColumnElementView(const IColumn& column) + : data(assert_cast(column).get_data()) {} +}; + +template <> +struct ColumnElementView { + using ColumnType = ColumnString; + using ElementType = StringRef; + const ColumnString& string_column; + ColumnElementView(const IColumn& column) + : string_column(assert_cast(column)) {} + StringRef get_element(size_t idx) const { return string_column.get_data_at(idx); } +}; + +// ColumnView is used to handle the nullable and const properties of a column. +// For example, a regular ColumnInt32 may appear in the following 4 cases: +// 1. ColumnInt32 +// 2. Const(ColumnInt32) +// 3. Nullable(ColumnInt32) +// 4. Const(Nullable(ColumnInt32)) (although this case is rare, it can still occur; many of our previous code did not consider this) +// You can use is_null_at and value_at to get the data at the corresponding position + +template +struct ColumnView { + const ColumnElementView data; + const NullMap* null_map; + const bool is_const; + const size_t count; + + static ColumnView create(const ColumnPtr& column_ptr) { + const auto& [from_data_column, is_const] = unpack_if_const(column_ptr); + const NullMap* null_map = nullptr; + const IColumn* data = nullptr; + if (const auto* nullable_column = + check_and_get_column(from_data_column.get())) { + null_map = &nullable_column->get_null_map_data(); + data = nullable_column->get_nested_column_ptr().get(); + } else { + data = from_data_column.get(); + } + + return ColumnView {.data = ColumnElementView(*data), + .null_map = null_map, + .is_const = is_const, + .count = column_ptr->size()}; + } + + bool is_null_at(size_t idx) const { + if (null_map != nullptr) { + return (*null_map)[is_const ? 0 : idx]; + } + return false; + } + + auto value_at(size_t idx) const { return data.get_element(is_const ? 0 : idx); } + + size_t size() const { return count; } +}; + +// CompileTimeColumnView builds upon ColumnView by using template parameters to distinguish +// const and nullable properties at compile time, so there is no need to check again during expression execution + +template +struct CompileTimeColumnView; + +template +struct CompileTimeColumnView { + const ColumnElementView& data; + + auto value_at(size_t idx) const { return data.get_element(idx); } + + bool is_null_at(size_t idx) const { return false; } +}; + +template +struct CompileTimeColumnView { + const ColumnElementView& data; + + auto value_at(size_t idx) const { return data.get_element(0); } + + bool is_null_at(size_t idx) const { return false; } +}; + +template +struct CompileTimeColumnView { + const ColumnElementView& data; + const NullMap& null_map; + + auto value_at(size_t idx) const { return data.get_element(idx); } + + bool is_null_at(size_t idx) const { return null_map[idx]; } +}; + +template +struct CompileTimeColumnView { + const ColumnElementView& data; + const NullMap& null_map; + + auto value_at(size_t idx) const { return data.get_element(0); } + + bool is_null_at(size_t idx) const { return null_map[0]; } +}; + +template +using CompileTimeColumnViewVariant = std::variant< + CompileTimeColumnView, CompileTimeColumnView, + CompileTimeColumnView, CompileTimeColumnView>; + +template +CompileTimeColumnViewVariant create_compile_time_column_view( + const ColumnView& col_view) { + if (col_view.null_map == nullptr) { + if (col_view.is_const) { + return CompileTimeColumnView {col_view.data}; + } else { + return CompileTimeColumnView {col_view.data}; + } + } else { + if (col_view.is_const) { + return CompileTimeColumnView {col_view.data, *(col_view.null_map)}; + } else { + return CompileTimeColumnView {col_view.data, *(col_view.null_map)}; + } + } +} + +template +using CompileTimeColumnViewVariantOnlyConst = + std::variant, + CompileTimeColumnView>; + +template +CompileTimeColumnViewVariantOnlyConst create_compile_time_column_view_only_const( + const ColumnView& col_view) { + DCHECK(col_view.null_map == nullptr) << "Only support non-nullable column"; + if (col_view.is_const) { + return CompileTimeColumnView {col_view.data}; + } else { + return CompileTimeColumnView {col_view.data}; + } +} + +// Utility tool for convenient column execution +// Can be used in functions/expressions +// If you don't want compile-time expansion, use execute_xxx_runtime functions +// If you want compile-time expansion, use execute_xxx_compile_time functions +// If the function has a native implementation that doesn't use use_default_implementation_for_nulls logic, +// use execute_xxx_compile_time functions (because the column may be nullable) +// If the function uses use_default_implementation_for_nulls logic, +// use execute_xxx_compile_time_only_const functions (because the column cannot be nullable) + +struct ExecuteColumn { + // unary + template + static void execute_unary_runtime(const ColumnPtr& col_a, NullFunc& null_func, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + execute_unary_impl(col_a_view, col_a_view.count, null_func, func); + } + + template + static void execute_unary_compile_time(const ColumnPtr& col_a, NullFunc& null_func, + Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + std::visit([&](auto&& a) { execute_unary_impl(a, col_a_view.count, null_func, func); }, + create_compile_time_column_view(col_a_view)); + } + + template + static void execute_unary_compile_time_only_const(const ColumnPtr& col_a, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + std::visit([&](auto&& a) { execute_unary_impl(a, col_a_view.count, not_null_func, func); }, + create_compile_time_column_view_only_const(col_a_view)); + } + + // binary + + template + static void execute_binary_runtime(const ColumnPtr& col_a, const ColumnPtr& col_b, + NullFunc& null_func, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + DCHECK(col_a_view.count == col_b_view.count); + execute_binary_impl(col_a_view, col_b_view, col_a_view.count, null_func, func); + } + + template + static void execute_binary_compile_time(const ColumnPtr& col_a, const ColumnPtr& col_b, + NullFunc& null_func, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + DCHECK(col_a_view.count == col_b_view.count); + size_t count = col_a_view.count; + std::visit([&](auto&& a, auto&& b) { execute_binary_impl(a, b, count, null_func, func); }, + create_compile_time_column_view(col_a_view), + create_compile_time_column_view(col_b_view)); + } + + template + static void execute_binary_compile_time_only_const(const ColumnPtr& col_a, + const ColumnPtr& col_b, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + DCHECK(col_a_view.count == col_b_view.count); + size_t count = col_a_view.count; + std::visit( + [&](auto&& a, auto&& b) { execute_binary_impl(a, b, count, not_null_func, func); }, + create_compile_time_column_view_only_const(col_a_view), + create_compile_time_column_view_only_const(col_b_view)); + } + + // ternary + + template + static void execute_ternary_runtime(const ColumnPtr& col_a, const ColumnPtr& col_b, + const ColumnPtr& col_c, NullFunc& null_func, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + ColumnView col_c_view = ColumnView::create(col_c); + DCHECK(col_a_view.count == col_b_view.count && col_a_view.count == col_c_view.count); + size_t count = col_a_view.count; + execute_ternary_impl(col_a_view, col_b_view, col_c_view, count, null_func, func); + } + + template + static void execute_ternary_compile_time(const ColumnPtr& col_a, const ColumnPtr& col_b, + const ColumnPtr& col_c, NullFunc& null_func, + Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + ColumnView col_c_view = ColumnView::create(col_c); + DCHECK(col_a_view.count == col_b_view.count && col_a_view.count == col_c_view.count); + size_t count = col_a_view.count; + std::visit([&](auto&& a, auto&& b, + auto&& c) { execute_ternary_impl(a, b, c, count, null_func, func); }, + create_compile_time_column_view(col_a_view), + create_compile_time_column_view(col_b_view), + create_compile_time_column_view(col_c_view)); + } + + template + static void execute_ternary_compile_time_only_const(const ColumnPtr& col_a, + const ColumnPtr& col_b, + const ColumnPtr& col_c, Func& func) { + ColumnView col_a_view = ColumnView::create(col_a); + ColumnView col_b_view = ColumnView::create(col_b); + ColumnView col_c_view = ColumnView::create(col_c); + DCHECK(col_a_view.count == col_b_view.count && col_a_view.count == col_c_view.count); + size_t count = col_a_view.count; + + std::visit([&](auto&& a, auto&& b, + auto&& c) { execute_ternary_impl(a, b, c, count, not_null_func, func); }, + create_compile_time_column_view_only_const(col_a_view), + create_compile_time_column_view_only_const(col_b_view), + create_compile_time_column_view_only_const(col_c_view)); + } + +private: + static void not_null_func(size_t i) { + throw doris::Exception(ErrorCode::INTERNAL_ERROR, + "no need to call null func for not nullable column"); + } + + template + static void execute_unary_impl(auto&& a, size_t count, NullFunc& null_func, Func& func) { + for (size_t i = 0; i < count; ++i) { + bool is_null_a = a.is_null_at(i); + if (is_null_a) { + null_func(i); + } else { + func(i, a.value_at(i)); + } + } + } + + template + static void execute_binary_impl(auto&& a, auto&& b, size_t count, NullFunc& null_func, + Func& func) { + for (size_t i = 0; i < count; ++i) { + bool is_null_a = a.is_null_at(i); + bool is_null_b = b.is_null_at(i); + if (is_null_a || is_null_b) { + null_func(i); + } else { + func(i, a.value_at(i), b.value_at(i)); + } + } + } + + template + static void execute_ternary_impl(auto&& a, auto&& b, auto&& c, size_t count, + NullFunc& null_func, Func& func) { + for (size_t i = 0; i < count; ++i) { + bool is_null_a = a.is_null_at(i); + bool is_null_b = b.is_null_at(i); + bool is_null_c = c.is_null_at(i); + if (is_null_a || is_null_b || is_null_c) { + null_func(i); + } else { + func(i, a.value_at(i), b.value_at(i), c.value_at(i)); + } + } + } +}; + +} // namespace doris::vectorized \ No newline at end of file diff --git a/be/src/vec/columns/column_string.cpp b/be/src/vec/columns/column_string.cpp index b94add09be5483..bcdb1335776fbb 100644 --- a/be/src/vec/columns/column_string.cpp +++ b/be/src/vec/columns/column_string.cpp @@ -53,20 +53,6 @@ void ColumnStr::sanity_check() const { #endif } -template -void ColumnStr::sanity_check_simple() const { -#ifndef NDEBUG - auto count = cast_set(offsets.size()); - if (chars.size() != offsets[count - 1]) { - throw Exception(Status::InternalError("row count: {}, chars.size(): {}, offset[{}]: {}", - count, chars.size(), count - 1, offsets[count - 1])); - } - if (offsets[-1] != 0) { - throw Exception(Status::InternalError("wrong offsets[-1]: {}", offsets[-1])); - } -#endif -} - template MutableColumnPtr ColumnStr::clone_resized(size_t to_size) const { auto res = ColumnStr::create(); diff --git a/be/src/vec/columns/column_string.h b/be/src/vec/columns/column_string.h index b885a1eba00918..3ff4426ec10f15 100644 --- a/be/src/vec/columns/column_string.h +++ b/be/src/vec/columns/column_string.h @@ -115,7 +115,19 @@ class ColumnStr final : public COWHelper> { bool is_variable_length() const override { return true; } void sanity_check() const override; - void sanity_check_simple() const; + void sanity_check_simple() const { +#ifndef NDEBUG + auto count = cast_set(offsets.size()); + if (chars.size() != offsets[count - 1]) { + throw Exception(Status::InternalError("row count: {}, chars.size(): {}, offset[{}]: {}", + count, chars.size(), count - 1, + offsets[count - 1])); + } + if (offsets[-1] != 0) { + throw Exception(Status::InternalError("wrong offsets[-1]: {}", offsets[-1])); + } +#endif + } std::string get_name() const override { return "String"; } diff --git a/be/src/vec/functions/function_ip.h b/be/src/vec/functions/function_ip.h index 07d39db085db85..c3b93757a1abbc 100644 --- a/be/src/vec/functions/function_ip.h +++ b/be/src/vec/functions/function_ip.h @@ -26,8 +26,10 @@ #include "common/cast_set.h" #include "olap/rowset/segment_v2/index_reader_helper.h" +#include "runtime/define_primitive_type.h" #include "vec/columns/column.h" #include "vec/columns/column_const.h" +#include "vec/columns/column_execute_util.h" #include "vec/columns/column_nullable.h" #include "vec/columns/column_string.h" #include "vec/columns/column_struct.h" @@ -1121,43 +1123,26 @@ class FunctionToIP : public IFunction { Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, uint32_t result, size_t input_rows_count) const override { - const auto& addr_column_with_type_and_name = block.get_by_position(arguments[0]); - const ColumnPtr& addr_column = addr_column_with_type_and_name.column; - const ColumnString* str_addr_column = nullptr; - const NullMap* addr_null_map = nullptr; - - if (addr_column_with_type_and_name.type->is_nullable()) { - const auto* addr_column_nullable = - assert_cast(addr_column.get()); - str_addr_column = assert_cast( - addr_column_nullable->get_nested_column_ptr().get()); - addr_null_map = &addr_column_nullable->get_null_map_data(); - } else { - str_addr_column = assert_cast(addr_column.get()); - } - auto col_res = ColumnVector::create(input_rows_count, 0); auto res_null_map = ColumnUInt8::create(input_rows_count, 0); auto& col_res_data = col_res->get_data(); auto& res_null_map_data = res_null_map->get_data(); - for (size_t i = 0; i < input_rows_count; ++i) { - if (addr_null_map && (*addr_null_map)[i]) { - if constexpr (exception_mode == IPConvertExceptionMode::Throw) { - throw Exception(ErrorCode::INVALID_ARGUMENT, - "The arguments of function {} must be String, not NULL", - get_name()); - } else if constexpr (exception_mode == IPConvertExceptionMode::Default) { - col_res_data[i] = 0; // '0.0.0.0' or '::' - continue; - } else { - res_null_map_data[i] = 1; - continue; - } + auto null_func = [&](size_t i) ALWAYS_INLINE { + if constexpr (exception_mode == IPConvertExceptionMode::Throw) { + throw Exception(ErrorCode::INVALID_ARGUMENT, + "The arguments of function {} must be String, not NULL", + get_name()); + } else if constexpr (exception_mode == IPConvertExceptionMode::Default) { + col_res_data[i] = 0; // '0.0.0.0' or '::' + } else { + res_null_map_data[i] = 1; } + }; + auto func = [&](size_t i, StringRef ip_str) ALWAYS_INLINE { if constexpr (PType == TYPE_IPV4) { - StringRef ipv4_str = str_addr_column->get_data_at(i); + StringRef ipv4_str = ip_str; IPv4 ipv4_val = 0; if (IPv4Value::from_string(ipv4_val, ipv4_str.data, ipv4_str.size)) { col_res_data[i] = ipv4_val; @@ -1172,7 +1157,7 @@ class FunctionToIP : public IFunction { } } } else { - StringRef ipv6_str = str_addr_column->get_data_at(i); + StringRef ipv6_str = ip_str; IPv6 ipv6_val = 0; if (IPv6Value::from_string(ipv6_val, ipv6_str.data, ipv6_str.size)) { col_res_data[i] = ipv6_val; @@ -1187,7 +1172,10 @@ class FunctionToIP : public IFunction { } } } - } + }; + + ExecuteColumn::execute_unary_compile_time( + block.get_by_position(arguments[0]).column, null_func, func); if constexpr (exception_mode == IPConvertExceptionMode::Null) { block.replace_by_position( diff --git a/be/src/vec/functions/functions_geo.cpp b/be/src/vec/functions/functions_geo.cpp index 01b98957795ea9..e50db7c79750ab 100644 --- a/be/src/vec/functions/functions_geo.cpp +++ b/be/src/vec/functions/functions_geo.cpp @@ -25,7 +25,9 @@ #include "geo/geo_common.h" #include "geo/geo_types.h" +#include "runtime/define_primitive_type.h" #include "vec/columns/column.h" +#include "vec/columns/column_execute_util.h" #include "vec/columns/column_nullable.h" #include "vec/common/assert_cast.h" #include "vec/common/string_ref.h" @@ -45,76 +47,34 @@ struct StPoint { static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { DCHECK_EQ(arguments.size(), 2); auto return_type = block.get_data_type(result); - - const auto& [left_column, left_const] = - unpack_if_const(block.get_by_position(arguments[0]).column); - const auto& [right_column, right_const] = - unpack_if_const(block.get_by_position(arguments[1]).column); - - const auto size = std::max(left_column->size(), right_column->size()); - + auto left_column_ptr = block.get_by_position(arguments[0]).column; + auto right_column_ptr = block.get_by_position(arguments[1]).column; + const auto size = std::max(left_column_ptr->size(), right_column_ptr->size()); auto res = ColumnString::create(); auto null_map = ColumnUInt8::create(size, 0); auto& null_map_data = null_map->get_data(); - const auto* left_column_f64 = assert_cast(left_column.get()); - const auto* right_column_f64 = assert_cast(right_column.get()); + GeoPoint point; std::string buf; - if (left_const) { - const_vector(left_column_f64, right_column_f64, res, null_map_data, size, point, buf); - } else if (right_const) { - vector_const(left_column_f64, right_column_f64, res, null_map_data, size, point, buf); - } else { - vector_vector(left_column_f64, right_column_f64, res, null_map_data, size, point, buf); - } + auto func = [&](size_t i, double left_val, double right_val) ALWAYS_INLINE { + auto geo_status = point.from_coord(left_val, right_val); + if (geo_status != GEO_PARSE_OK) { + null_map_data[i] = 1; + res->insert_default(); + return; + } + buf.clear(); + point.encode_to(&buf); + res->insert_data(buf.data(), buf.size()); + }; + + ExecuteColumn::execute_binary_compile_time_only_const( + left_column_ptr, right_column_ptr, func); block.replace_by_position(result, ColumnNullable::create(std::move(res), std::move(null_map))); return Status::OK(); } - - static void loop_do(GeoParseStatus& cur_res, ColumnString::MutablePtr& res, NullMap& null_map, - int row, GeoPoint& point, std::string& buf) { - if (cur_res != GEO_PARSE_OK) { - null_map[row] = 1; - res->insert_default(); - return; - } - - buf.clear(); - point.encode_to(&buf); - res->insert_data(buf.data(), buf.size()); - } - - static void const_vector(const ColumnFloat64* left_column, const ColumnFloat64* right_column, - ColumnString::MutablePtr& res, NullMap& null_map, const size_t size, - GeoPoint& point, std::string& buf) { - double x = left_column->get_element(0); - for (int row = 0; row < size; ++row) { - auto cur_res = point.from_coord(x, right_column->get_element(row)); - loop_do(cur_res, res, null_map, row, point, buf); - } - } - - static void vector_const(const ColumnFloat64* left_column, const ColumnFloat64* right_column, - ColumnString::MutablePtr& res, NullMap& null_map, const size_t size, - GeoPoint& point, std::string& buf) { - double y = right_column->get_element(0); - for (int row = 0; row < size; ++row) { - auto cur_res = point.from_coord(left_column->get_element(row), y); - loop_do(cur_res, res, null_map, row, point, buf); - } - } - - static void vector_vector(const ColumnFloat64* left_column, const ColumnFloat64* right_column, - ColumnString::MutablePtr& res, NullMap& null_map, const size_t size, - GeoPoint& point, std::string& buf) { - for (int row = 0; row < size; ++row) { - auto cur_res = - point.from_coord(left_column->get_element(row), right_column->get_element(row)); - loop_do(cur_res, res, null_map, row, point, buf); - } - } }; struct StAsTextName { @@ -242,31 +202,20 @@ struct StDistanceSphere { DCHECK_EQ(arguments.size(), 4); auto return_type = block.get_data_type(result); - ColumnPtr x_lng_origin = - block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); - ColumnPtr x_lat_origin = - block.get_by_position(arguments[1]).column->convert_to_full_column_if_const(); - ColumnPtr y_lng_origin = - block.get_by_position(arguments[2]).column->convert_to_full_column_if_const(); - ColumnPtr y_lat_origin = - block.get_by_position(arguments[3]).column->convert_to_full_column_if_const(); - - const auto* x_lng = check_and_get_column(x_lng_origin.get()); - const auto* x_lat = check_and_get_column(x_lat_origin.get()); - const auto* y_lng = check_and_get_column(y_lng_origin.get()); - const auto* y_lat = check_and_get_column(y_lat_origin.get()); - CHECK(x_lng && x_lat && y_lng && y_lat); - - const auto size = x_lng->size(); + auto x_lng = ColumnView::create(block.get_by_position(arguments[0]).column); + auto x_lat = ColumnView::create(block.get_by_position(arguments[1]).column); + auto y_lng = ColumnView::create(block.get_by_position(arguments[2]).column); + auto y_lat = ColumnView::create(block.get_by_position(arguments[3]).column); + + const auto size = x_lng.size(); auto res = ColumnFloat64::create(); res->reserve(size); auto null_map = ColumnUInt8::create(size, 0); auto& null_map_data = null_map->get_data(); for (int row = 0; row < size; ++row) { double distance = 0; - if (!GeoPoint::ComputeDistance(x_lng->get_element(row), x_lat->get_element(row), - y_lng->get_element(row), y_lat->get_element(row), - &distance)) { + if (!GeoPoint::ComputeDistance(x_lng.value_at(row), x_lat.value_at(row), + y_lng.value_at(row), y_lat.value_at(row), &distance)) { null_map_data[row] = 1; res->insert_default(); continue; @@ -288,22 +237,12 @@ struct StAngleSphere { DCHECK_EQ(arguments.size(), 4); auto return_type = block.get_data_type(result); - ColumnPtr x_lng_origin = - block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); - ColumnPtr x_lat_origin = - block.get_by_position(arguments[1]).column->convert_to_full_column_if_const(); - ColumnPtr y_lng_origin = - block.get_by_position(arguments[2]).column->convert_to_full_column_if_const(); - ColumnPtr y_lat_origin = - block.get_by_position(arguments[3]).column->convert_to_full_column_if_const(); - - const auto* x_lng = check_and_get_column(x_lng_origin.get()); - const auto* x_lat = check_and_get_column(x_lat_origin.get()); - const auto* y_lng = check_and_get_column(y_lng_origin.get()); - const auto* y_lat = check_and_get_column(y_lat_origin.get()); - CHECK(x_lng && x_lat && y_lng && y_lat); + auto x_lng = ColumnView::create(block.get_by_position(arguments[0]).column); + auto x_lat = ColumnView::create(block.get_by_position(arguments[1]).column); + auto y_lng = ColumnView::create(block.get_by_position(arguments[2]).column); + auto y_lat = ColumnView::create(block.get_by_position(arguments[3]).column); - const auto size = x_lng->size(); + const auto size = x_lng.size(); auto res = ColumnFloat64::create(); res->reserve(size); @@ -312,9 +251,8 @@ struct StAngleSphere { for (int row = 0; row < size; ++row) { double angle = 0; - if (!GeoPoint::ComputeAngleSphere(x_lng->get_element(row), x_lat->get_element(row), - y_lng->get_element(row), y_lat->get_element(row), - &angle)) { + if (!GeoPoint::ComputeAngleSphere(x_lng.value_at(row), x_lat.value_at(row), + y_lng.value_at(row), y_lat.value_at(row), &angle)) { null_map_data[row] = 1; res->insert_default(); continue; @@ -336,9 +274,10 @@ struct StAngle { DCHECK_EQ(arguments.size(), 3); auto return_type = block.get_data_type(result); - auto p1 = block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); - auto p2 = block.get_by_position(arguments[1]).column->convert_to_full_column_if_const(); - auto p3 = block.get_by_position(arguments[2]).column->convert_to_full_column_if_const(); + auto p1 = block.get_by_position(arguments[0]).column; + auto p2 = block.get_by_position(arguments[1]).column; + auto p3 = block.get_by_position(arguments[2]).column; + const auto size = p1->size(); auto res = ColumnFloat64::create(); res->reserve(size); @@ -349,38 +288,40 @@ struct StAngle { GeoPoint point2; GeoPoint point3; - for (int row = 0; row < size; ++row) { - auto shape_value1 = p1->get_data_at(row); + auto func = [&](size_t i, StringRef shape_value1, StringRef shape_value2, + StringRef shape_value3) ALWAYS_INLINE { auto pt1 = point1.decode_from(shape_value1.data, shape_value1.size); if (!pt1) { - null_map_data[row] = 1; + null_map_data[i] = 1; res->insert_default(); - continue; + return; } - auto shape_value2 = p2->get_data_at(row); auto pt2 = point2.decode_from(shape_value2.data, shape_value2.size); if (!pt2) { - null_map_data[row] = 1; + null_map_data[i] = 1; res->insert_default(); - continue; + return; } - auto shape_value3 = p3->get_data_at(row); auto pt3 = point3.decode_from(shape_value3.data, shape_value3.size); if (!pt3) { - null_map_data[row] = 1; + null_map_data[i] = 1; res->insert_default(); - continue; + return; } double angle = 0; if (!GeoPoint::ComputeAngle(&point1, &point2, &point3, &angle)) { - null_map_data[row] = 1; + null_map_data[i] = 1; res->insert_default(); - continue; + return; } res->insert_value(angle); - } + }; + + ExecuteColumn::execute_ternary_compile_time_only_const(p1, p2, p3, func); + block.replace_by_position(result, ColumnNullable::create(std::move(res), std::move(null_map))); return Status::OK(); @@ -395,10 +336,8 @@ struct StAzimuth { DCHECK_EQ(arguments.size(), 2); auto return_type = block.get_data_type(result); - const auto& [left_column, left_const] = - unpack_if_const(block.get_by_position(arguments[0]).column); - const auto& [right_column, right_const] = - unpack_if_const(block.get_by_position(arguments[1]).column); + auto left_column = block.get_by_position(arguments[0]).column; + auto right_column = block.get_by_position(arguments[1]).column; const auto size = std::max(left_column->size(), right_column->size()); auto res = ColumnFloat64::create(); @@ -407,20 +346,23 @@ struct StAzimuth { auto& null_map_data = null_map->get_data(); GeoPoint point1; GeoPoint point2; - if (left_const) { - const_vector(left_column, right_column, res, null_map_data, size, point1, point2); - } else if (right_const) { - vector_const(left_column, right_column, res, null_map_data, size, point1, point2); - } else { - vector_vector(left_column, right_column, res, null_map_data, size, point1, point2); - } + + auto func = [&](size_t i, StringRef shape_value1, StringRef shape_value2) ALWAYS_INLINE { + auto pt1 = point1.decode_from(shape_value1.data, shape_value1.size); + auto pt2 = point2.decode_from(shape_value2.data, shape_value2.size); + + loop_do(pt1, pt2, point1, point2, res, null_map_data, i); + }; + ExecuteColumn::execute_binary_compile_time_only_const( + left_column, right_column, func); + block.replace_by_position(result, ColumnNullable::create(std::move(res), std::move(null_map))); return Status::OK(); } static void loop_do(bool& pt1, bool& pt2, GeoPoint& point1, GeoPoint& point2, - ColumnFloat64::MutablePtr& res, NullMap& null_map, int row) { + ColumnFloat64::MutablePtr& res, NullMap& null_map, size_t row) { if (!(pt1 && pt2)) { null_map[row] = 1; res->insert_default(); @@ -435,45 +377,6 @@ struct StAzimuth { } res->insert_value(angle); } - - static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnFloat64::MutablePtr& res, NullMap& null_map, size_t size, - GeoPoint& point1, GeoPoint& point2) { - auto shape_value1 = left_column->get_data_at(0); - auto pt1 = point1.decode_from(shape_value1.data, shape_value1.size); - for (int row = 0; row < size; ++row) { - auto shape_value2 = right_column->get_data_at(row); - auto pt2 = point2.decode_from(shape_value2.data, shape_value2.size); - - loop_do(pt1, pt2, point1, point2, res, null_map, row); - } - } - - static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnFloat64::MutablePtr& res, NullMap& null_map, size_t size, - GeoPoint& point1, GeoPoint& point2) { - auto shape_value2 = right_column->get_data_at(0); - auto pt2 = point2.decode_from(shape_value2.data, shape_value2.size); - for (int row = 0; row < size; ++row) { - auto shape_value1 = left_column->get_data_at(row); - auto pt1 = point1.decode_from(shape_value1.data, shape_value1.size); - - loop_do(pt1, pt2, point1, point2, res, null_map, row); - } - } - - static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnFloat64::MutablePtr& res, NullMap& null_map, size_t size, - GeoPoint& point1, GeoPoint& point2) { - for (int row = 0; row < size; ++row) { - auto shape_value1 = left_column->get_data_at(row); - auto pt1 = point1.decode_from(shape_value1.data, shape_value1.size); - auto shape_value2 = right_column->get_data_at(row); - auto pt2 = point2.decode_from(shape_value2.data, shape_value2.size); - - loop_do(pt1, pt2, point1, point2, res, null_map, row); - } - } }; struct StAreaSquareMeters { @@ -565,39 +468,36 @@ struct StCircle { static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { DCHECK_EQ(arguments.size(), 3); auto return_type = block.get_data_type(result); - auto center_lng = - block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); - const auto* center_lng_ptr = assert_cast(center_lng.get()); - auto center_lat = - block.get_by_position(arguments[1]).column->convert_to_full_column_if_const(); - const auto* center_lat_ptr = assert_cast(center_lat.get()); - auto radius = block.get_by_position(arguments[2]).column->convert_to_full_column_if_const(); - const auto* radius_ptr = assert_cast(radius.get()); + auto center_lng_col = block.get_by_position(arguments[0]).column; + auto center_lat_col = block.get_by_position(arguments[1]).column; + auto radius_col = block.get_by_position(arguments[2]).column; - const auto size = center_lng->size(); + const auto size = center_lng_col->size(); auto res = ColumnString::create(); - auto null_map = ColumnUInt8::create(size, 0); auto& null_map_data = null_map->get_data(); GeoCircle circle; std::string buf; - for (int row = 0; row < size; ++row) { - auto lng_value = center_lng_ptr->get_element(row); - auto lat_value = center_lat_ptr->get_element(row); - auto radius_value = radius_ptr->get_element(row); - auto value = circle.init(lng_value, lat_value, radius_value); - if (value != GEO_PARSE_OK) { - null_map_data[row] = 1; - res->insert_default(); - continue; - } - buf.clear(); - circle.encode_to(&buf); - res->insert_data(buf.data(), buf.size()); - } + auto func = [&](size_t i, double lng_value, double lat_value, double radius_value) + ALWAYS_INLINE { + auto value = circle.init(lng_value, lat_value, radius_value); + if (value != GEO_PARSE_OK) { + null_map_data[i] = 1; + res->insert_default(); + return; + } + buf.clear(); + circle.encode_to(&buf); + res->insert_data(buf.data(), buf.size()); + }; + + ExecuteColumn::execute_ternary_compile_time_only_const( + center_lng_col, center_lat_col, radius_col, func); + block.replace_by_position(result, ColumnNullable::create(std::move(res), std::move(null_map))); return Status::OK(); @@ -613,75 +513,37 @@ struct StRelationFunction { static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) { DCHECK_EQ(arguments.size(), 2); auto return_type = block.get_data_type(result); - const auto& [left_column, left_const] = - unpack_if_const(block.get_by_position(arguments[0]).column); - const auto& [right_column, right_const] = - unpack_if_const(block.get_by_position(arguments[1]).column); + auto left_column = block.get_by_position(arguments[0]).column; + auto right_column = block.get_by_position(arguments[1]).column; - const auto size = std::max(left_column->size(), right_column->size()); + const auto size = left_column->size(); auto res = ColumnUInt8::create(size, 0); auto null_map = ColumnUInt8::create(size, 0); auto& null_map_data = null_map->get_data(); - if (left_const) { - const_vector(left_column, right_column, res, null_map_data, size); - } else if (right_const) { - vector_const(left_column, right_column, res, null_map_data, size); - } else { - vector_vector(left_column, right_column, res, null_map_data, size); - } - block.replace_by_position(result, - ColumnNullable::create(std::move(res), std::move(null_map))); - return Status::OK(); - } - - static void loop_do(StringRef& lhs_value, StringRef& rhs_value, - std::vector>& shapes, - ColumnUInt8::MutablePtr& res, NullMap& null_map, int row) { - StringRef* strs[2] = {&lhs_value, &rhs_value}; - for (int i = 0; i < 2; ++i) { - std::unique_ptr shape(GeoShape::from_encoded(strs[i]->data, strs[i]->size)); - shapes[i] = std::move(shape); - if (!shapes[i]) { - null_map[row] = 1; - break; + auto func = [&](size_t i, StringRef lhs_value, StringRef rhs_value) ALWAYS_INLINE { + std::unique_ptr shape1( + GeoShape::from_encoded(lhs_value.data, lhs_value.size)); + if (!shape1) { + null_map_data[i] = 1; + return; } - } - if (shapes[0] && shapes[1]) { - auto relation_value = Func::evaluate(shapes[0].get(), shapes[1].get()); - res->get_data()[row] = relation_value; - } - } - - static void const_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { - auto lhs_value = left_column->get_data_at(0); - std::vector> shapes(2); - for (int row = 0; row < size; ++row) { - auto rhs_value = right_column->get_data_at(row); - loop_do(lhs_value, rhs_value, shapes, res, null_map, row); - } - } + std::unique_ptr shape2( + GeoShape::from_encoded(rhs_value.data, rhs_value.size)); + if (!shape2) { + null_map_data[i] = 1; + return; + } + res->get_data()[i] = Func::evaluate(shape1.get(), shape2.get()); + }; - static void vector_const(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { - auto rhs_value = right_column->get_data_at(0); - std::vector> shapes(2); - for (int row = 0; row < size; ++row) { - auto lhs_value = left_column->get_data_at(row); - loop_do(lhs_value, rhs_value, shapes, res, null_map, row); - } - } + ExecuteColumn::execute_binary_compile_time_only_const( + left_column, right_column, func); - static void vector_vector(const ColumnPtr& left_column, const ColumnPtr& right_column, - ColumnUInt8::MutablePtr& res, NullMap& null_map, const size_t size) { - std::vector> shapes(2); - for (int row = 0; row < size; ++row) { - auto lhs_value = left_column->get_data_at(row); - auto rhs_value = right_column->get_data_at(row); - loop_do(lhs_value, rhs_value, shapes, res, null_map, row); - } + block.replace_by_position(result, + ColumnNullable::create(std::move(res), std::move(null_map))); + return Status::OK(); } }; diff --git a/be/test/vec/columns/column_execute_util_test.cpp b/be/test/vec/columns/column_execute_util_test.cpp new file mode 100644 index 00000000000000..3f15a9e02a4f2e --- /dev/null +++ b/be/test/vec/columns/column_execute_util_test.cpp @@ -0,0 +1,687 @@ +// 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 "vec/columns/column_execute_util.h" + +#include + +#include "runtime/primitive_type.h" +#include "testutil/column_helper.h" +#include "vec/columns/column.h" +#include "vec/columns/column_const.h" +#include "vec/columns/column_string.h" +#include "vec/data_types/data_type_nullable.h" +#include "vec/data_types/data_type_number.h" +#include "vec/data_types/data_type_string.h" + +namespace doris::vectorized { + +// ==================== ColumnElementView Tests ==================== + +TEST(ColumnExecuteUtilTest, ColumnElementView_int32) { + auto col = ColumnHelper::create_column({10, 20, 30}); + ColumnElementView view(*col); + + EXPECT_EQ(view.get_element(0), 10); + EXPECT_EQ(view.get_element(1), 20); + EXPECT_EQ(view.get_element(2), 30); +} + +TEST(ColumnExecuteUtilTest, ColumnElementView_int64) { + auto col = ColumnHelper::create_column({100, 200, 300}); + ColumnElementView view(*col); + + EXPECT_EQ(view.get_element(0), 100); + EXPECT_EQ(view.get_element(1), 200); + EXPECT_EQ(view.get_element(2), 300); +} + +TEST(ColumnExecuteUtilTest, ColumnElementView_double) { + auto col = ColumnHelper::create_column({1.5, 2.5, 3.5}); + ColumnElementView view(*col); + + EXPECT_DOUBLE_EQ(view.get_element(0), 1.5); + EXPECT_DOUBLE_EQ(view.get_element(1), 2.5); + EXPECT_DOUBLE_EQ(view.get_element(2), 3.5); +} + +TEST(ColumnExecuteUtilTest, ColumnElementView_string) { + auto col = ColumnHelper::create_column({"hello", "world", "test"}); + ColumnElementView view(*col); + + EXPECT_EQ(view.get_element(0).to_string(), "hello"); + EXPECT_EQ(view.get_element(1).to_string(), "world"); + EXPECT_EQ(view.get_element(2).to_string(), "test"); +} + +// ==================== ColumnView Tests ==================== + +TEST(ColumnExecuteUtilTest, ColumnView_non_nullable_non_const) { + auto col = ColumnHelper::create_column({1, 2, 3}); + auto view = ColumnView::create(col); + + EXPECT_EQ(view.count, 3); + EXPECT_FALSE(view.is_const); + EXPECT_EQ(view.null_map, nullptr); + + EXPECT_FALSE(view.is_null_at(0)); + EXPECT_FALSE(view.is_null_at(1)); + EXPECT_FALSE(view.is_null_at(2)); + + EXPECT_EQ(view.value_at(0), 1); + EXPECT_EQ(view.value_at(1), 2); + EXPECT_EQ(view.value_at(2), 3); +} + +TEST(ColumnExecuteUtilTest, ColumnView_nullable) { + auto col = ColumnHelper::create_nullable_column({10, 20, 30}, {0, 1, 0}); + auto view = ColumnView::create(col); + + EXPECT_EQ(view.count, 3); + EXPECT_FALSE(view.is_const); + EXPECT_NE(view.null_map, nullptr); + + EXPECT_FALSE(view.is_null_at(0)); + EXPECT_TRUE(view.is_null_at(1)); + EXPECT_FALSE(view.is_null_at(2)); + + EXPECT_EQ(view.value_at(0), 10); + EXPECT_EQ(view.value_at(2), 30); +} + +TEST(ColumnExecuteUtilTest, ColumnView_const_column) { + auto col = ColumnHelper::create_column({42}); + ColumnPtr const_col = ColumnConst::create(col, 5); + auto view = ColumnView::create(const_col); + + EXPECT_EQ(view.count, 5); + EXPECT_TRUE(view.is_const); + EXPECT_EQ(view.null_map, nullptr); + + // All positions should return the same value + for (size_t i = 0; i < 5; ++i) { + EXPECT_FALSE(view.is_null_at(i)); + EXPECT_EQ(view.value_at(i), 42); + } +} + +TEST(ColumnExecuteUtilTest, ColumnView_const_nullable_column) { + auto col = ColumnHelper::create_nullable_column({100}, {0}); + ColumnPtr const_col = ColumnConst::create(col, 3); + auto view = ColumnView::create(const_col); + + EXPECT_EQ(view.count, 3); + EXPECT_TRUE(view.is_const); + EXPECT_NE(view.null_map, nullptr); + + for (size_t i = 0; i < 3; ++i) { + EXPECT_FALSE(view.is_null_at(i)); + EXPECT_EQ(view.value_at(i), 100); + } +} + +TEST(ColumnExecuteUtilTest, ColumnView_const_nullable_null_column) { + auto col = ColumnHelper::create_nullable_column({0}, {1}); + ColumnPtr const_col = ColumnConst::create(col, 3); + auto view = ColumnView::create(const_col); + + EXPECT_EQ(view.count, 3); + EXPECT_TRUE(view.is_const); + EXPECT_NE(view.null_map, nullptr); + + for (size_t i = 0; i < 3; ++i) { + EXPECT_TRUE(view.is_null_at(i)); + } +} + +TEST(ColumnExecuteUtilTest, ColumnView_string) { + auto col = ColumnHelper::create_column({"a", "bb", "ccc"}); + auto view = ColumnView::create(col); + + EXPECT_EQ(view.count, 3); + EXPECT_FALSE(view.is_const); + EXPECT_EQ(view.null_map, nullptr); + + EXPECT_EQ(view.value_at(0).to_string(), "a"); + EXPECT_EQ(view.value_at(1).to_string(), "bb"); + EXPECT_EQ(view.value_at(2).to_string(), "ccc"); +} + +// ==================== CompileTimeColumnView Tests ==================== + +TEST(ColumnExecuteUtilTest, CompileTimeColumnView_non_const_non_nullable) { + auto col = ColumnHelper::create_column({1, 2, 3}); + auto view = ColumnView::create(col); + auto ct_view = create_compile_time_column_view(view); + + EXPECT_EQ(ct_view.index(), 0); // CompileTimeColumnView + + std::visit( + [](auto&& v) { + EXPECT_EQ(v.value_at(0), 1); + EXPECT_EQ(v.value_at(1), 2); + EXPECT_EQ(v.value_at(2), 3); + EXPECT_FALSE(v.is_null_at(0)); + }, + ct_view); +} + +TEST(ColumnExecuteUtilTest, CompileTimeColumnView_const_non_nullable) { + auto col = ColumnHelper::create_column({99}); + ColumnPtr const_col = ColumnConst::create(col, 4); + auto view = ColumnView::create(const_col); + auto ct_view = create_compile_time_column_view(view); + + EXPECT_EQ(ct_view.index(), 1); // CompileTimeColumnView + + std::visit( + [](auto&& v) { + // const column returns same value for all indices + EXPECT_EQ(v.value_at(0), 99); + EXPECT_EQ(v.value_at(10), 99); // Index doesn't matter for const + EXPECT_FALSE(v.is_null_at(0)); + }, + ct_view); +} + +TEST(ColumnExecuteUtilTest, CompileTimeColumnView_non_const_nullable) { + auto col = ColumnHelper::create_nullable_column({5, 6, 7}, {0, 1, 0}); + auto view = ColumnView::create(col); + auto ct_view = create_compile_time_column_view(view); + + EXPECT_EQ(ct_view.index(), 2); // CompileTimeColumnView + + std::visit( + [](auto&& v) { + EXPECT_EQ(v.value_at(0), 5); + EXPECT_EQ(v.value_at(2), 7); + EXPECT_FALSE(v.is_null_at(0)); + EXPECT_TRUE(v.is_null_at(1)); + EXPECT_FALSE(v.is_null_at(2)); + }, + ct_view); +} + +TEST(ColumnExecuteUtilTest, CompileTimeColumnView_const_nullable) { + auto col = ColumnHelper::create_nullable_column({50}, {0}); + ColumnPtr const_col = ColumnConst::create(col, 3); + auto view = ColumnView::create(const_col); + auto ct_view = create_compile_time_column_view(view); + + EXPECT_EQ(ct_view.index(), 3); // CompileTimeColumnView + + std::visit( + [](auto&& v) { + EXPECT_EQ(v.value_at(0), 50); + EXPECT_EQ(v.value_at(1), 50); + EXPECT_FALSE(v.is_null_at(0)); + }, + ct_view); +} + +TEST(ColumnExecuteUtilTest, CompileTimeColumnViewOnlyConst_non_const) { + auto col = ColumnHelper::create_column({1, 2, 3}); + auto view = ColumnView::create(col); + auto ct_view = create_compile_time_column_view_only_const(view); + + EXPECT_EQ(ct_view.index(), 0); // CompileTimeColumnView +} + +TEST(ColumnExecuteUtilTest, CompileTimeColumnViewOnlyConst_const) { + auto col = ColumnHelper::create_column({42}); + ColumnPtr const_col = ColumnConst::create(col, 3); + auto view = ColumnView::create(const_col); + auto ct_view = create_compile_time_column_view_only_const(view); + + EXPECT_EQ(ct_view.index(), 1); // CompileTimeColumnView +} + +// ==================== ExecuteColumn Unary Tests ==================== + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_runtime_basic) { + auto col = ColumnHelper::create_column({1, 2, 3}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 val) { results.push_back(val * 2); }; + + ExecuteColumn::execute_unary_runtime(col, null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 2); + EXPECT_EQ(results[1], 4); + EXPECT_EQ(results[2], 6); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_runtime_nullable) { + auto col = ColumnHelper::create_nullable_column({10, 20, 30}, {0, 1, 0}); + std::vector results; + std::vector null_indices; + + auto null_func = [&null_indices](size_t i) { null_indices.push_back(i); }; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_runtime(col, null_func, func); + + ASSERT_EQ(results.size(), 2); + EXPECT_EQ(results[0], 10); + EXPECT_EQ(results[1], 30); + + ASSERT_EQ(null_indices.size(), 1); + EXPECT_EQ(null_indices[0], 1); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_runtime_const) { + auto col = ColumnHelper::create_column({5}); + ColumnPtr const_col = ColumnConst::create(col, 4); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_runtime(const_col, null_func, func); + + ASSERT_EQ(results.size(), 4); + for (auto r : results) { + EXPECT_EQ(r, 5); + } +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_compile_time) { + auto col = ColumnHelper::create_column({100, 200, 300}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int64 val) { results.push_back(val + 1); }; + + ExecuteColumn::execute_unary_compile_time(col, null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 101); + EXPECT_EQ(results[1], 201); + EXPECT_EQ(results[2], 301); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_compile_time_nullable) { + auto col = ColumnHelper::create_nullable_column({1, 2, 3, 4}, {0, 1, 1, 0}); + std::vector results; + std::vector null_indices; + + auto null_func = [&null_indices](size_t i) { null_indices.push_back(i); }; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_compile_time(col, null_func, func); + + ASSERT_EQ(results.size(), 2); + EXPECT_EQ(results[0], 1); + EXPECT_EQ(results[1], 4); + + ASSERT_EQ(null_indices.size(), 2); + EXPECT_EQ(null_indices[0], 1); + EXPECT_EQ(null_indices[1], 2); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_compile_time_only_const) { + auto col = ColumnHelper::create_column({7, 8, 9}); + std::vector results; + + auto func = [&results](size_t i, Int32 val) { results.push_back(val * 10); }; + + ExecuteColumn::execute_unary_compile_time_only_const(col, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 70); + EXPECT_EQ(results[1], 80); + EXPECT_EQ(results[2], 90); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_unary_string) { + auto col = ColumnHelper::create_column({"abc", "defg", "hi"}); + std::vector lengths; + + auto null_func = [](size_t i) {}; + auto func = [&lengths](size_t i, StringRef val) { lengths.push_back(val.size); }; + + ExecuteColumn::execute_unary_runtime(col, null_func, func); + + ASSERT_EQ(lengths.size(), 3); + EXPECT_EQ(lengths[0], 3); + EXPECT_EQ(lengths[1], 4); + EXPECT_EQ(lengths[2], 2); +} + +// ==================== ExecuteColumn Binary Tests ==================== + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_runtime_basic) { + auto col_a = ColumnHelper::create_column({1, 2, 3}); + auto col_b = ColumnHelper::create_column({10, 20, 30}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b) { results.push_back(a + b); }; + + ExecuteColumn::execute_binary_runtime(col_a, col_b, null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 11); + EXPECT_EQ(results[1], 22); + EXPECT_EQ(results[2], 33); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_runtime_with_nulls) { + auto col_a = ColumnHelper::create_nullable_column({1, 2, 3}, {0, 1, 0}); + auto col_b = ColumnHelper::create_nullable_column({10, 20, 30}, {0, 0, 1}); + std::vector results; + std::vector null_indices; + + auto null_func = [&null_indices](size_t i) { null_indices.push_back(i); }; + auto func = [&results](size_t i, Int32 a, Int32 b) { results.push_back(a * b); }; + + ExecuteColumn::execute_binary_runtime(col_a, col_b, null_func, func); + + // Only index 0 should have valid data (both non-null) + ASSERT_EQ(results.size(), 1); + EXPECT_EQ(results[0], 10); + + // Index 1 has null in col_a, index 2 has null in col_b + ASSERT_EQ(null_indices.size(), 2); + EXPECT_EQ(null_indices[0], 1); + EXPECT_EQ(null_indices[1], 2); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_compile_time) { + auto col_a = ColumnHelper::create_column({2, 4, 6}); + auto col_b = ColumnHelper::create_column({1, 2, 3}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b) { results.push_back(a - b); }; + + ExecuteColumn::execute_binary_compile_time(col_a, col_b, null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 1); + EXPECT_EQ(results[1], 2); + EXPECT_EQ(results[2], 3); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_compile_time_const_and_vector) { + auto col_a = ColumnHelper::create_column({10}); + ColumnPtr const_col_a = ColumnConst::create(col_a, 3); + auto col_b = ColumnHelper::create_column({1, 2, 3}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b) { results.push_back(a + b); }; + + ExecuteColumn::execute_binary_compile_time(const_col_a, col_b, null_func, + func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 11); + EXPECT_EQ(results[1], 12); + EXPECT_EQ(results[2], 13); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_compile_time_only_const) { + auto col_a = ColumnHelper::create_column({1.0, 2.0, 3.0}); + auto col_b = ColumnHelper::create_column({0.5, 1.5, 2.5}); + std::vector results; + + auto func = [&results](size_t i, double a, double b) { results.push_back(a * b); }; + + ExecuteColumn::execute_binary_compile_time_only_const(col_a, col_b, + func); + + ASSERT_EQ(results.size(), 3); + EXPECT_DOUBLE_EQ(results[0], 0.5); + EXPECT_DOUBLE_EQ(results[1], 3.0); + EXPECT_DOUBLE_EQ(results[2], 7.5); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_binary_different_types) { + auto col_a = ColumnHelper::create_column({1, 2, 3}); + auto col_b = ColumnHelper::create_column({1.5, 2.5, 3.5}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, double b) { results.push_back(a + b); }; + + ExecuteColumn::execute_binary_compile_time(col_a, col_b, null_func, + func); + + ASSERT_EQ(results.size(), 3); + EXPECT_DOUBLE_EQ(results[0], 2.5); + EXPECT_DOUBLE_EQ(results[1], 4.5); + EXPECT_DOUBLE_EQ(results[2], 6.5); +} + +// ==================== ExecuteColumn Ternary Tests ==================== + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_runtime_basic) { + auto col_a = ColumnHelper::create_column({1, 2, 3}); + auto col_b = ColumnHelper::create_column({10, 20, 30}); + auto col_c = ColumnHelper::create_column({100, 200, 300}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b, Int32 c) { results.push_back(a + b + c); }; + + ExecuteColumn::execute_ternary_runtime(col_a, col_b, col_c, + null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 111); + EXPECT_EQ(results[1], 222); + EXPECT_EQ(results[2], 333); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_runtime_with_nulls) { + auto col_a = ColumnHelper::create_nullable_column({1, 2, 3}, {1, 0, 0}); + auto col_b = ColumnHelper::create_nullable_column({10, 20, 30}, {0, 1, 0}); + auto col_c = ColumnHelper::create_nullable_column({100, 200, 300}, {0, 0, 1}); + std::vector results; + std::vector null_indices; + + auto null_func = [&null_indices](size_t i) { null_indices.push_back(i); }; + auto func = [&results](size_t i, Int32 a, Int32 b, Int32 c) { results.push_back(a + b + c); }; + + ExecuteColumn::execute_ternary_runtime(col_a, col_b, col_c, + null_func, func); + + // All indices have at least one null + EXPECT_TRUE(results.empty()); + ASSERT_EQ(null_indices.size(), 3); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_compile_time) { + auto col_a = ColumnHelper::create_column({1, 2}); + auto col_b = ColumnHelper::create_column({3, 4}); + auto col_c = ColumnHelper::create_column({5, 6}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b, Int32 c) { results.push_back(a * b * c); }; + + ExecuteColumn::execute_ternary_compile_time(col_a, col_b, col_c, + null_func, func); + + ASSERT_EQ(results.size(), 2); + EXPECT_EQ(results[0], 15); // 1 * 3 * 5 + EXPECT_EQ(results[1], 48); // 2 * 4 * 6 +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_compile_time_with_const) { + auto col_a = ColumnHelper::create_column({10}); + ColumnPtr const_col_a = ColumnConst::create(col_a, 3); + auto col_b = ColumnHelper::create_column({1, 2, 3}); + auto col_c = ColumnHelper::create_column({100, 200, 300}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 a, Int32 b, Int32 c) { results.push_back(a + b + c); }; + + ExecuteColumn::execute_ternary_compile_time( + const_col_a, col_b, col_c, null_func, func); + + ASSERT_EQ(results.size(), 3); + EXPECT_EQ(results[0], 111); // 10 + 1 + 100 + EXPECT_EQ(results[1], 212); // 10 + 2 + 200 + EXPECT_EQ(results[2], 313); // 10 + 3 + 300 +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_compile_time_only_const) { + auto col_a = ColumnHelper::create_column({1, 2}); + auto col_b = ColumnHelper::create_column({3, 4}); + auto col_c = ColumnHelper::create_column({5, 6}); + std::vector results; + + auto func = [&results](size_t i, Int32 a, Int32 b, Int32 c) { results.push_back(a + b - c); }; + + ExecuteColumn::execute_ternary_compile_time_only_const( + col_a, col_b, col_c, func); + + ASSERT_EQ(results.size(), 2); + EXPECT_EQ(results[0], -1); // 1 + 3 - 5 + EXPECT_EQ(results[1], 0); // 2 + 4 - 6 +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_ternary_string) { + auto col_a = ColumnHelper::create_column({"hello", "world"}); + auto col_b = ColumnHelper::create_column({" ", "-"}); + auto col_c = ColumnHelper::create_column({"world", "hello"}); + std::vector results; + + auto func = [&results](size_t i, StringRef a, StringRef b, StringRef c) { + results.push_back(a.to_string() + b.to_string() + c.to_string()); + }; + + ExecuteColumn::execute_ternary_compile_time_only_const( + col_a, col_b, col_c, func); + + ASSERT_EQ(results.size(), 2); + EXPECT_EQ(results[0], "hello world"); + EXPECT_EQ(results[1], "world-hello"); +} + +// ==================== Edge Cases ==================== + +TEST(ColumnExecuteUtilTest, ExecuteColumn_empty_column) { + auto col = ColumnHelper::create_column({}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_runtime(col, null_func, func); + + EXPECT_TRUE(results.empty()); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_all_nulls) { + auto col = ColumnHelper::create_nullable_column({1, 2, 3}, {1, 1, 1}); + std::vector results; + std::vector null_indices; + + auto null_func = [&null_indices](size_t i) { null_indices.push_back(i); }; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_runtime(col, null_func, func); + + EXPECT_TRUE(results.empty()); + ASSERT_EQ(null_indices.size(), 3); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_single_element) { + auto col = ColumnHelper::create_column({42}); + std::vector results; + + auto null_func = [](size_t i) {}; + auto func = [&results](size_t i, Int32 val) { results.push_back(val); }; + + ExecuteColumn::execute_unary_compile_time(col, null_func, func); + + ASSERT_EQ(results.size(), 1); + EXPECT_EQ(results[0], 42); +} + +// ==================== Real-world Use Case Tests ==================== + +TEST(ColumnExecuteUtilTest, ExecuteColumn_real_use_case_double_values) { + // Simulate doubling all values in a column + auto col = ColumnHelper::create_column({10, 20, 30, 40, 50}); + auto result_col = ColumnInt64::create(); + result_col->get_data().resize(5); + + auto func = [&result_col](size_t i, Int64 val) { result_col->get_data()[i] = val * 2; }; + + ExecuteColumn::execute_unary_compile_time_only_const(col, func); + + EXPECT_EQ(result_col->get_data()[0], 20); + EXPECT_EQ(result_col->get_data()[1], 40); + EXPECT_EQ(result_col->get_data()[2], 60); + EXPECT_EQ(result_col->get_data()[3], 80); + EXPECT_EQ(result_col->get_data()[4], 100); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_real_use_case_binary_add_with_null_handling) { + // Simulate binary addition with null handling + auto col_a = ColumnHelper::create_nullable_column({1, 2, 3, 4}, {0, 1, 0, 0}); + auto col_b = + ColumnHelper::create_nullable_column({10, 20, 30, 40}, {0, 0, 1, 0}); + + auto result_col = ColumnInt32::create(4, 0); + auto result_null_map = ColumnUInt8::create(4, 0); + + auto null_func = [&result_null_map](size_t i) { result_null_map->get_data()[i] = 1; }; + auto func = [&result_col](size_t i, Int32 a, Int32 b) { result_col->get_data()[i] = a + b; }; + + ExecuteColumn::execute_binary_compile_time(col_a, col_b, null_func, func); + + // Check results + EXPECT_EQ(result_col->get_data()[0], 11); + EXPECT_EQ(result_null_map->get_data()[0], 0); + + EXPECT_EQ(result_null_map->get_data()[1], 1); // null because col_a[1] is null + + EXPECT_EQ(result_null_map->get_data()[2], 1); // null because col_b[2] is null + + EXPECT_EQ(result_col->get_data()[3], 44); + EXPECT_EQ(result_null_map->get_data()[3], 0); +} + +TEST(ColumnExecuteUtilTest, ExecuteColumn_real_use_case_const_broadcast) { + // Test const column broadcast - common scenario in SQL like "SELECT col + 10" + auto col = ColumnHelper::create_column({1, 2, 3, 4, 5}); + auto const_val = ColumnHelper::create_column({10}); + ColumnPtr const_col = ColumnConst::create(const_val, 5); + + auto result_col = ColumnInt32::create(); + result_col->get_data().resize(5); + + auto func = [&result_col](size_t i, Int32 a, Int32 b) { result_col->get_data()[i] = a + b; }; + + ExecuteColumn::execute_binary_compile_time_only_const(col, const_col, func); + + EXPECT_EQ(result_col->get_data()[0], 11); + EXPECT_EQ(result_col->get_data()[1], 12); + EXPECT_EQ(result_col->get_data()[2], 13); + EXPECT_EQ(result_col->get_data()[3], 14); + EXPECT_EQ(result_col->get_data()[4], 15); +} + +} // namespace doris::vectorized