This is an automated email from the ASF dual-hosted git repository.

Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ccb4b81e6a [Optimization](array) Avoid materializing const array 
columns in element_at (#64175)
3ccb4b81e6a is described below

commit 3ccb4b81e6a5d740e79921aa1c094e8cb4640b03
Author: HappenLee <[email protected]>
AuthorDate: Mon Jun 8 11:25:54 2026 +0800

    [Optimization](array) Avoid materializing const array columns in element_at 
(#64175)
    
    ## Summary
    - Skip `convert_to_full_column_if_const()` for the array argument in
    `element_at` when it's a `ColumnConst`, using `unpack_if_const()` +
    `index_check_const()` instead to avoid O(N) memory expansion.
    - Add `is_const_array` parameter to `_execute_nullable`,
    `_execute_number`, `_execute_string`, and `_execute_common` methods.
    - Add unit tests covering const int32/string arrays, null arrays, null
    indices, and large batches (4096 rows).
    
    ## Test plan
    - BE UT: `function_array_element_test.cpp` — 5 new test cases covering
    const array scenarios.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../exprs/function/array/function_array_element.h  | 114 ++++++----
 .../exprs/function/function_array_element_test.cpp | 230 +++++++++++++++++++++
 2 files changed, 301 insertions(+), 43 deletions(-)

diff --git a/be/src/exprs/function/array/function_array_element.h 
b/be/src/exprs/function/array/function_array_element.h
index 065f0f5fd18..2a932479862 100644
--- a/be/src/exprs/function/array/function_array_element.h
+++ b/be/src/exprs/function/array/function_array_element.h
@@ -105,25 +105,42 @@ public:
         UInt8* dst_null_map = dst_null_column->get_data().data();
         const UInt8* src_null_map = nullptr;
         ColumnsWithTypeAndName args;
-        block.replace_by_position(
-                arguments[0],
-                
block.get_by_position(arguments[0]).column->convert_to_full_column_if_const());
-        auto col_left = block.get_by_position(arguments[0]);
-        if (col_left.column->is_nullable()) {
-            const auto* null_col = assert_cast<const 
ColumnNullable*>(col_left.column.get());
-            src_null_map = null_col->get_null_map_column().get_data().data();
-            args = {{null_col->get_nested_column_ptr(), 
remove_nullable(col_left.type),
-                     col_left.name},
-                    block.get_by_position(arguments[1])};
-        } else {
-            args = {col_left, block.get_by_position(arguments[1])};
-        }
         ColumnPtr res_column = nullptr;
-        if (is_column<ColumnMap>(args[0].column.get()) ||
-            check_column_const<ColumnMap>(args[0].column.get())) {
+
+        auto col_left_raw = block.get_by_position(arguments[0]);
+        // Map element lookup requires row-aligned offsets; keep original path 
for map type.
+        if (remove_nullable(col_left_raw.type)->get_primitive_type() == 
TYPE_MAP) {
+            block.replace_by_position(arguments[0],
+                                      
col_left_raw.column->convert_to_full_column_if_const());
+            auto col_left = block.get_by_position(arguments[0]);
+            if (col_left.column->is_nullable()) {
+                const auto* null_col = assert_cast<const 
ColumnNullable*>(col_left.column.get());
+                src_null_map = 
null_col->get_null_map_column().get_data().data();
+                args = {{null_col->get_nested_column_ptr(), 
remove_nullable(col_left.type),
+                         col_left.name},
+                        block.get_by_position(arguments[1])};
+            } else {
+                args = {col_left, block.get_by_position(arguments[1])};
+            }
             res_column = _execute_map(args, input_rows_count, src_null_map, 
dst_null_map);
         } else {
-            res_column = _execute_nullable(args, input_rows_count, 
src_null_map, dst_null_map);
+            // Array element access: avoid materializing a constant array 
column.
+            // A literal like [[1],[2]] becomes ColumnConst with a single-row 
inner column;
+            // unpack_if_const() gives us that inner column plus a constancy 
flag so inner
+            // loops can use index_check_const() instead of expanding N copies.
+            auto [unpacked_col, is_const_array] = 
unpack_if_const(col_left_raw.column);
+            if (unpacked_col->is_nullable()) {
+                const auto* null_col = assert_cast<const 
ColumnNullable*>(unpacked_col.get());
+                src_null_map = 
null_col->get_null_map_column().get_data().data();
+                args = {{null_col->get_nested_column_ptr(), 
remove_nullable(col_left_raw.type),
+                         col_left_raw.name},
+                        block.get_by_position(arguments[1])};
+            } else {
+                args = {{unpacked_col, col_left_raw.type, col_left_raw.name},
+                        block.get_by_position(arguments[1])};
+            }
+            res_column = _execute_nullable(args, input_rows_count, 
src_null_map, dst_null_map,
+                                           is_const_array);
         }
         if (!res_column) {
             return Status::RuntimeError("unsupported types for function {}({}, 
{})", get_name(),
@@ -173,21 +190,23 @@ private:
     ColumnPtr _execute_number(const ColumnArray::Offsets64& offsets, const 
IColumn& nested_column,
                               const UInt8* arr_null_map, const IColumn& 
indices,
                               const UInt8* nested_null_map, UInt8* 
dst_null_map,
-                              const UInt8* idx_null_map, bool is_const_index) 
const {
+                              const UInt8* idx_null_map, bool is_const_index, 
bool is_const_array,
+                              size_t input_rows_count) const {
         const auto& nested_data = reinterpret_cast<const 
ColumnType&>(nested_column).get_data();
         const auto& index_data = assert_cast<const 
IndexColumnType&>(indices).get_data();
 
         auto dst_column = nested_column.clone_empty();
         auto& dst_data = reinterpret_cast<ColumnType&>(*dst_column).get_data();
-        dst_data.resize(offsets.size());
+        dst_data.resize(input_rows_count);
 
-        for (size_t row = 0; row < offsets.size(); ++row) {
-            size_t off = row == 0 ? 0 : offsets[row - 1];
-            size_t len = offsets[row] - off;
+        for (size_t row = 0; row < input_rows_count; ++row) {
+            size_t arr_row = index_check_const(row, is_const_array);
+            size_t off = arr_row == 0 ? 0 : offsets[arr_row - 1];
+            size_t len = offsets[arr_row] - off;
             size_t idx = index_check_const(row, is_const_index);
             auto index =
                     (idx_null_map && idx_null_map[idx]) ? 0 : 
static_cast<Int64>(index_data[idx]);
-            bool null_flag = bool(arr_null_map && arr_null_map[row]);
+            bool null_flag = bool(arr_null_map && arr_null_map[arr_row]);
             if (!null_flag && index > 0 && index <= len) {
                 index += off - 1;
             } else if (!null_flag && index < 0 && -index <= len) {
@@ -208,7 +227,8 @@ private:
     ColumnPtr _execute_string(const ColumnArray::Offsets64& offsets, const 
IColumn& nested_column,
                               const UInt8* arr_null_map, const IColumn& 
indices,
                               const UInt8* nested_null_map, UInt8* 
dst_null_map,
-                              const UInt8* idx_null_map, bool is_const_index) 
const {
+                              const UInt8* idx_null_map, bool is_const_index, 
bool is_const_array,
+                              size_t input_rows_count) const {
         const auto& src_str_offs =
                 reinterpret_cast<const 
ColumnString&>(nested_column).get_offsets();
         const auto& src_str_chars =
@@ -218,17 +238,18 @@ private:
         // prepare return data
         auto dst_column = ColumnString::create();
         auto& dst_str_offs = dst_column->get_offsets();
-        dst_str_offs.resize(offsets.size());
+        dst_str_offs.resize(input_rows_count);
         auto& dst_str_chars = dst_column->get_chars();
         dst_str_chars.reserve(src_str_chars.size());
 
-        for (size_t row = 0; row < offsets.size(); ++row) {
-            size_t off = row == 0 ? 0 : offsets[row - 1];
-            size_t len = offsets[row] - off;
+        for (size_t row = 0; row < input_rows_count; ++row) {
+            size_t arr_row = index_check_const(row, is_const_array);
+            size_t off = arr_row == 0 ? 0 : offsets[arr_row - 1];
+            size_t len = offsets[arr_row] - off;
             size_t idx = index_check_const(row, is_const_index);
             auto index =
                     (idx_null_map && idx_null_map[idx]) ? 0 : 
static_cast<Int64>(index_data[idx]);
-            bool null_flag = bool(arr_null_map && arr_null_map[row]);
+            bool null_flag = bool(arr_null_map && arr_null_map[arr_row]);
             if (!null_flag && index > 0 && index <= len) {
                 index += off - 1;
             } else if (!null_flag && index < 0 && -index <= len) {
@@ -277,26 +298,28 @@ private:
         ColumnWithTypeAndName data(std::move(val_arr), 
std::make_shared<DataTypeArray>(val_type),
                                    "value");
         ColumnsWithTypeAndName args = {data, indices};
-        return _execute_nullable(args, input_rows_count, src_null_map, 
dst_null_map);
+        return _execute_nullable(args, input_rows_count, src_null_map, 
dst_null_map, false);
     }
 
     template <typename IndexColumnType>
     ColumnPtr _execute_common(const ColumnArray::Offsets64& offsets, const 
IColumn& nested_column,
                               const UInt8* arr_null_map, const IColumn& 
indices,
                               const UInt8* nested_null_map, UInt8* 
dst_null_map,
-                              const UInt8* idx_null_map, bool is_const_index) 
const {
+                              const UInt8* idx_null_map, bool is_const_index, 
bool is_const_array,
+                              size_t input_rows_count) const {
         const auto& index_data = assert_cast<const 
IndexColumnType&>(indices).get_data();
 
         auto dst_column = nested_column.clone_empty();
-        dst_column->reserve(offsets.size());
+        dst_column->reserve(input_rows_count);
 
-        for (size_t row = 0; row < offsets.size(); ++row) {
-            size_t off = row == 0 ? 0 : offsets[row - 1];
-            size_t len = offsets[row] - off;
+        for (size_t row = 0; row < input_rows_count; ++row) {
+            size_t arr_row = index_check_const(row, is_const_array);
+            size_t off = arr_row == 0 ? 0 : offsets[arr_row - 1];
+            size_t len = offsets[arr_row] - off;
             size_t idx = index_check_const(row, is_const_index);
             auto index =
                     (idx_null_map && idx_null_map[idx]) ? 0 : 
static_cast<Int64>(index_data[idx]);
-            bool null_flag = bool(arr_null_map && arr_null_map[row]);
+            bool null_flag = bool(arr_null_map && arr_null_map[arr_row]);
             if (!null_flag && index > 0 && index <= len) {
                 index += off - 1;
             } else if (!null_flag && index < 0 && -index <= len) {
@@ -319,12 +342,14 @@ private:
     }
 
     ColumnPtr _execute_nullable(const ColumnsWithTypeAndName& arguments, 
size_t input_rows_count,
-                                const UInt8* src_null_map, UInt8* 
dst_null_map) const {
-        // check array nested column type and get data
-        auto left_column = 
arguments[0].column->convert_to_full_column_if_const();
-        const auto& array_column = assert_cast<const 
ColumnArray&>(*left_column);
+                                const UInt8* src_null_map, UInt8* dst_null_map,
+                                bool is_const_array) const {
+        // arguments[0].column is already the raw ColumnArray (possibly size-1 
when is_const_array).
+        // Do NOT call convert_to_full_column_if_const() here; const-awareness 
is handled below
+        // via index_check_const(row, is_const_array).
+        const auto& array_column = assert_cast<const 
ColumnArray&>(*arguments[0].column);
         const auto& offsets = array_column.get_offsets();
-        DCHECK(offsets.size() == input_rows_count);
+        DCHECK(is_const_array ? offsets.size() == 1 : offsets.size() == 
input_rows_count);
         const UInt8* nested_null_map = nullptr;
         ColumnPtr nested_column = nullptr;
         if (is_column_nullable(array_column.get_data())) {
@@ -362,19 +387,22 @@ private:
                 using DataDispatchType = std::decay_t<decltype(data_type)>;
                 res = _execute_number<typename DataDispatchType::ColumnType, 
IndexColumnType>(
                         offsets, *nested_column, src_null_map, *idx_col_raw, 
nested_null_map,
-                        dst_null_map, idx_null_map, is_const_index);
+                        dst_null_map, idx_null_map, is_const_index, 
is_const_array,
+                        input_rows_count);
                 return true;
             };
 
             if (is_string_type(left_element_type->get_primitive_type())) {
                 res = _execute_string<IndexColumnType>(offsets, 
*nested_column, src_null_map,
                                                        *idx_col_raw, 
nested_null_map, dst_null_map,
-                                                       idx_null_map, 
is_const_index);
+                                                       idx_null_map, 
is_const_index, is_const_array,
+                                                       input_rows_count);
             } else if 
(!dispatch_switch_scalar(left_element_type->get_primitive_type(),
                                                data_call)) {
                 res = _execute_common<IndexColumnType>(offsets, 
*nested_column, src_null_map,
                                                        *idx_col_raw, 
nested_null_map, dst_null_map,
-                                                       idx_null_map, 
is_const_index);
+                                                       idx_null_map, 
is_const_index, is_const_array,
+                                                       input_rows_count);
             }
             return true;
         };
diff --git a/be/test/exprs/function/function_array_element_test.cpp 
b/be/test/exprs/function/function_array_element_test.cpp
index 784188f2e43..7d3c9bceccf 100644
--- a/be/test/exprs/function/function_array_element_test.cpp
+++ b/be/test/exprs/function/function_array_element_test.cpp
@@ -17,13 +17,21 @@
 
 #include <string>
 
+#include "core/column/column_array.h"
+#include "core/column/column_const.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
 #include "core/data_type/data_type_date.h"
 #include "core/data_type/data_type_date_time.h"
 #include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_nullable.h"
 #include "core/data_type/data_type_number.h"
 #include "core/data_type/data_type_string.h"
 #include "core/types.h"
 #include "exprs/function/function_test_util.h"
+#include "exprs/function/simple_function_factory.h"
 
 namespace doris {
 
@@ -131,4 +139,226 @@ TEST(function_array_element_test, element_at) {
     }
 }
 
+// Helper: build a ColumnConst wrapping a single Array(Int32) value 
[values...].
+// The apparent size of the returned ColumnConst is `apparent_size`.
+static ColumnPtr make_const_int32_array(std::vector<Int32> values, size_t 
apparent_size) {
+    auto data_col = ColumnInt32::create();
+    for (auto v : values) {
+        data_col->insert_value(v);
+    }
+    auto offsets = ColumnArray::ColumnOffsets::create();
+    offsets->insert_value(static_cast<Int64>(values.size()));
+    auto arr = ColumnArray::create(std::move(data_col), std::move(offsets));
+    // element_at always produces Nullable, so use a non-null wrapper
+    auto null_map = ColumnUInt8::create(1, 0 /*not null*/);
+    auto nullable_arr = ColumnNullable::create(std::move(arr), 
std::move(null_map));
+    return ColumnConst::create(std::move(nullable_arr), apparent_size);
+}
+
+// Helper: build a ColumnConst wrapping a single Array(String) value 
[values...].
+static ColumnPtr make_const_string_array(std::vector<std::string> values, 
size_t apparent_size) {
+    auto data_col = ColumnString::create();
+    for (const auto& v : values) {
+        data_col->insert_data(v.data(), v.size());
+    }
+    auto offsets = ColumnArray::ColumnOffsets::create();
+    offsets->insert_value(static_cast<Int64>(values.size()));
+    auto arr = ColumnArray::create(std::move(data_col), std::move(offsets));
+    auto null_map = ColumnUInt8::create(1, 0);
+    auto nullable_arr = ColumnNullable::create(std::move(arr), 
std::move(null_map));
+    return ColumnConst::create(std::move(nullable_arr), apparent_size);
+}
+
+// Invoke element_at(arr_col, idx_col) and return the result ColumnNullable.
+// arr_type must match arr_col; idx_type must match idx_col.
+static ColumnPtr run_element_at(ColumnPtr arr_col, DataTypePtr arr_type, 
ColumnPtr idx_col,
+                                DataTypePtr idx_type, DataTypePtr result_type,
+                                size_t input_rows_count) {
+    ColumnsWithTypeAndName args = {{arr_col, arr_type, "arr"}, {idx_col, 
idx_type, "idx"}};
+    auto func = SimpleFunctionFactory::instance().get_function("element_at", 
args, result_type);
+    EXPECT_NE(func, nullptr);
+
+    Block block;
+    block.insert({arr_col, arr_type, "arr"});
+    block.insert({idx_col, idx_type, "idx"});
+    block.insert({nullptr, result_type, "result"});
+
+    EXPECT_TRUE(func->execute(nullptr, block, {0, 1}, 2, 
input_rows_count).ok());
+    return block.get_by_position(2).column;
+}
+
+// Tests for element_at with a constant (ColumnConst) array argument and a 
varying index.
+// The key invariant: no row-count copies of the const array should be made; 
the function
+// must produce correct results using the single stored value.
+TEST(function_array_element_test, element_at_const_int32_array_varying_index) {
+    // Const array: [10, 20, 30]  (same for every row)
+    // Indices:     [ 1,  2,  3,  4, -1, -3, -4,  0]
+    // Expected:    [10, 20, 30, NG, 30, 10, NG, NG]   (NG = NULL)
+    constexpr size_t N = 8;
+
+    auto arr_type =
+            
make_nullable(std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+    auto const_arr = make_const_int32_array({10, 20, 30}, N);
+
+    auto idx_data = ColumnInt32::create();
+    for (Int32 v : {1, 2, 3, 4, -1, -3, -4, 0}) {
+        idx_data->insert_value(v);
+    }
+    auto idx_null_map = ColumnUInt8::create(N, 0);
+    auto idx_col = ColumnNullable::create(std::move(idx_data), 
std::move(idx_null_map));
+    auto idx_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+    auto result_type = make_nullable(std::make_shared<DataTypeInt32>());
+    auto result = run_element_at(const_arr, arr_type, std::move(idx_col), 
idx_type, result_type, N);
+
+    ASSERT_EQ(result->size(), N);
+    const auto& nr = assert_cast<const ColumnNullable&>(*result);
+    const auto& data = assert_cast<const ColumnInt32&>(nr.get_nested_column());
+
+    struct Expected {
+        bool is_null;
+        Int32 val;
+    };
+    const Expected expected[N] = {{false, 10}, {false, 20}, {false, 30}, 
{true, 0},
+                                  {false, 30}, {false, 10}, {true, 0},   
{true, 0}};
+    for (size_t i = 0; i < N; ++i) {
+        EXPECT_EQ(nr.is_null_at(i), expected[i].is_null) << "row " << i;
+        if (!expected[i].is_null) {
+            EXPECT_EQ(data.get_element(i), expected[i].val) << "row " << i;
+        }
+    }
+}
+
+// Const array where the array itself is NULL → every row must return NULL.
+TEST(function_array_element_test, element_at_const_null_array) {
+    constexpr size_t N = 4;
+
+    auto arr_type =
+            
make_nullable(std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+
+    // Build a ColumnConst wrapping a size-1 Nullable(Array) that IS null.
+    auto inner_data = ColumnInt32::create();
+    auto inner_offsets = ColumnArray::ColumnOffsets::create();
+    inner_offsets->insert_value(0);
+    auto inner_arr = ColumnArray::create(std::move(inner_data), 
std::move(inner_offsets));
+    auto null_map = ColumnUInt8::create(1, 1 /*null*/);
+    auto nullable_arr = ColumnNullable::create(std::move(inner_arr), 
std::move(null_map));
+    ColumnPtr const_arr = ColumnConst::create(std::move(nullable_arr), N);
+
+    auto idx_data = ColumnInt32::create();
+    for (Int32 v : {1, 1, 1, 1}) {
+        idx_data->insert_value(v);
+    }
+    auto idx_null_map = ColumnUInt8::create(N, 0);
+    auto idx_col = ColumnNullable::create(std::move(idx_data), 
std::move(idx_null_map));
+    auto idx_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+    auto result_type = make_nullable(std::make_shared<DataTypeInt32>());
+    auto result = run_element_at(const_arr, arr_type, std::move(idx_col), 
idx_type, result_type, N);
+
+    ASSERT_EQ(result->size(), N);
+    const auto& nr = assert_cast<const ColumnNullable&>(*result);
+    for (size_t i = 0; i < N; ++i) {
+        EXPECT_TRUE(nr.is_null_at(i)) << "row " << i;
+    }
+}
+
+// Const array with nullable index: NULL index → NULL result.
+TEST(function_array_element_test, element_at_const_array_null_index) {
+    constexpr size_t N = 4;
+
+    auto arr_type =
+            
make_nullable(std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+    auto const_arr = make_const_int32_array({10, 20, 30}, N);
+
+    auto idx_data = ColumnInt32::create();
+    for (Int32 v : {1, 1, 1, 1}) {
+        idx_data->insert_value(v);
+    }
+    // All indices are NULL
+    auto idx_null_map = ColumnUInt8::create(N, 1 /*null*/);
+    auto idx_col = ColumnNullable::create(std::move(idx_data), 
std::move(idx_null_map));
+    auto idx_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+    auto result_type = make_nullable(std::make_shared<DataTypeInt32>());
+    auto result = run_element_at(const_arr, arr_type, std::move(idx_col), 
idx_type, result_type, N);
+
+    ASSERT_EQ(result->size(), N);
+    const auto& nr = assert_cast<const ColumnNullable&>(*result);
+    for (size_t i = 0; i < N; ++i) {
+        EXPECT_TRUE(nr.is_null_at(i)) << "row " << i;
+    }
+}
+
+// Const Array(String) – exercises _execute_string code path.
+TEST(function_array_element_test, element_at_const_string_array_varying_index) 
{
+    constexpr size_t N = 5;
+
+    auto arr_type =
+            
make_nullable(std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>()));
+    auto const_arr = make_const_string_array({"hello", "world", ""}, N);
+
+    auto idx_data = ColumnInt32::create();
+    for (Int32 v : {1, 2, 3, 4, -1}) {
+        idx_data->insert_value(v);
+    }
+    auto idx_null_map = ColumnUInt8::create(N, 0);
+    auto idx_col = ColumnNullable::create(std::move(idx_data), 
std::move(idx_null_map));
+    auto idx_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+    auto result_type = make_nullable(std::make_shared<DataTypeString>());
+    auto result = run_element_at(const_arr, arr_type, std::move(idx_col), 
idx_type, result_type, N);
+
+    ASSERT_EQ(result->size(), N);
+    const auto& nr = assert_cast<const ColumnNullable&>(*result);
+    const auto& str_col = assert_cast<const 
ColumnString&>(nr.get_nested_column());
+
+    EXPECT_FALSE(nr.is_null_at(0));
+    EXPECT_EQ(str_col.get_data_at(0), std::string_view("hello"));
+    EXPECT_FALSE(nr.is_null_at(1));
+    EXPECT_EQ(str_col.get_data_at(1), std::string_view("world"));
+    EXPECT_FALSE(nr.is_null_at(2));
+    EXPECT_EQ(str_col.get_data_at(2), std::string_view("", 0));
+    EXPECT_TRUE(nr.is_null_at(3)); // index 4 is out of bounds
+    EXPECT_FALSE(nr.is_null_at(4));
+    EXPECT_EQ(str_col.get_data_at(4), std::string_view("", 0)); // -1 → last = 
""
+}
+
+// Large batch: verifies const-array optimization is correct for batch_size > 
1.
+TEST(function_array_element_test, element_at_const_array_large_batch) {
+    constexpr size_t N = 4096;
+
+    auto arr_type =
+            
make_nullable(std::make_shared<DataTypeArray>(std::make_shared<DataTypeInt32>()));
+    // Const array: [100, 200, 300]
+    auto const_arr = make_const_int32_array({100, 200, 300}, N);
+
+    // Indices cycle through: 1→100, 2→200, 3→300, 4→NULL
+    auto idx_data = ColumnInt32::create();
+    idx_data->reserve(N);
+    for (size_t i = 0; i < N; ++i) {
+        idx_data->insert_value(static_cast<Int32>(i % 4 + 1));
+    }
+    auto idx_null_map = ColumnUInt8::create(N, 0);
+    auto idx_col = ColumnNullable::create(std::move(idx_data), 
std::move(idx_null_map));
+    auto idx_type = make_nullable(std::make_shared<DataTypeInt32>());
+
+    auto result_type = make_nullable(std::make_shared<DataTypeInt32>());
+    auto result = run_element_at(const_arr, arr_type, std::move(idx_col), 
idx_type, result_type, N);
+
+    ASSERT_EQ(result->size(), N);
+    const auto& nr = assert_cast<const ColumnNullable&>(*result);
+    const auto& data = assert_cast<const ColumnInt32&>(nr.get_nested_column());
+
+    const Int32 expected_vals[4] = {100, 200, 300, 0 /*null*/};
+    const bool expected_null[4] = {false, false, false, true};
+    for (size_t i = 0; i < N; ++i) {
+        size_t slot = i % 4;
+        ASSERT_EQ(nr.is_null_at(i), expected_null[slot]) << "row " << i;
+        if (!expected_null[slot]) {
+            ASSERT_EQ(data.get_element(i), expected_vals[slot]) << "row " << i;
+        }
+    }
+}
+
 } // namespace doris


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to