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

HappenLee 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 a4423dc2a02 [opt](column) reuse nullable column metadata during 
function execution (#66031)
a4423dc2a02 is described below

commit a4423dc2a02cb60fd45c4f31343f768715b0100d
Author: Mryange <[email protected]>
AuthorDate: Mon Aug 3 14:24:53 2026 +0800

    [opt](column) reuse nullable column metadata during function execution 
(#66031)
    
    The default nullable function path independently inspected the same null
    maps when checking for all-NULL arguments, unnesting nullable inputs,
    and wrapping function results. This caused redundant full null-map scans
    and made type-level nullable checks difficult to distinguish from
    runtime NULL-value checks.
    
    This change introduces `NullableColumnInfo` to collect the nested
    column, typed null-map column, constness, and non-NULL count once per
    input column. The information is reused across nullable unnesting, CAST
    handling, and result wrapping. It also renames the type-level helper to
    `has_nullable_argument_type` and preserves the existing copy-on-write
    behavior by copying nested data only when NULL payloads must be replaced
    with defaults.
---
 be/src/core/block/column_with_type_and_name.cpp    |  83 +++++++-----
 be/src/core/block/column_with_type_and_name.h      |  20 ++-
 be/src/core/column/column.h                        |  11 ++
 be/src/core/column/column_const.h                  |   7 +
 be/src/core/column/column_nullable.cpp             |  23 ++++
 be/src/core/column/column_nullable.h               |   8 ++
 be/src/exprs/function/cast/function_cast.cpp       |  27 +++-
 be/src/exprs/function/function.cpp                 |  59 ++++++---
 be/src/exprs/function/function.h                   |   3 +
 .../core/block/column_with_type_and_name_test.cpp  | 142 ++++++++++++++++++++-
 .../exprs/function/function_arithmetic_test.cpp    |   8 ++
 11 files changed, 331 insertions(+), 60 deletions(-)

diff --git a/be/src/core/block/column_with_type_and_name.cpp 
b/be/src/core/block/column_with_type_and_name.cpp
index ec3f4a61386..917bfd3d142 100644
--- a/be/src/core/block/column_with_type_and_name.cpp
+++ b/be/src/core/block/column_with_type_and_name.cpp
@@ -30,10 +30,10 @@
 #include "core/column/column.h"
 #include "core/column/column_const.h"
 #include "core/column/column_nothing.h"
+#include "core/column/column_nullable.h"
 #include "core/data_type/data_type.h"
 #include "core/data_type/data_type_nullable.h"
 #include "core/types.h"
-#include "util/simd/bits.h"
 
 namespace doris {
 
@@ -105,41 +105,58 @@ void 
ColumnWithTypeAndName::to_pb_column_meta(PColumnMeta* col_meta) const {
     type->to_pb_column_meta(col_meta);
 }
 
+const ColumnNullable& ColumnWithTypeAndName::get_nullable_column() const {
+    DCHECK(type->is_nullable());
+    DCHECK(column);
+    const auto& [physical_column, _] = unpack_if_const(column);
+    return assert_cast<const ColumnNullable&, 
TypeCheckOnRelease::DISABLE>(*physical_column);
+}
+
+const ColumnUInt8::Ptr& ColumnWithTypeAndName::get_nullable_null_map_column() 
const {
+    return get_nullable_column().get_null_map_column_ptr();
+}
+
+NullableColumnInfo ColumnWithTypeAndName::get_nullable_column_info() const {
+    DCHECK(type->is_nullable());
+    DCHECK(column);
+
+    const auto [has_null, only_null] = 
get_nullable_column().get_null_map_state();
+    return {.has_null = has_null,
+            .only_null = only_null,
+            .is_const = is_column_const(*column),
+            .is_nullable = true};
+}
+
 ColumnWithTypeAndName ColumnWithTypeAndName::unnest_nullable(
-        bool replace_null_data_to_default) const {
-    if (type->is_nullable()) {
-        auto nested_type =
-                assert_cast<const DataTypeNullable*, 
TypeCheckOnRelease::DISABLE>(type.get())
-                        ->get_nested_type();
-        ColumnPtr nested_column = column;
-        if (column) {
-            // A column_ptr is needed here to ensure that the column in 
convert_to_full_column_if_const is not released.
-            auto [column_ptr, is_const] = unpack_if_const(column);
-            const auto* source_column =
-                    assert_cast<const ColumnNullable*, 
TypeCheckOnRelease::DISABLE>(
-                            column_ptr.get());
-            if (is_const) {
-                nested_column =
-                        
ColumnConst::create(source_column->get_nested_column_ptr(), column->size());
-            } else {
-                nested_column = source_column->get_nested_column_ptr();
-            }
-
-            if (replace_null_data_to_default) {
-                const auto& null_map = source_column->get_null_map_data();
-                // only need to mutate nested column, avoid to copy nullmap
-                auto mutable_nested_col = (*std::move(nested_column)).mutate();
-                if (simd::contain_one(null_map.data(), null_map.size())) {
-                    
mutable_nested_col->replace_column_null_data(null_map.data());
-                }
-
-                return {std::move(mutable_nested_col), nested_type, ""};
-            }
-        }
-        return {nested_column, nested_type, ""};
-    } else {
+        const NullableColumnInfo& info, bool replace_null_data_to_default) 
const {
+    if (!type->is_nullable()) {
         return {column, type, ""};
     }
+    DCHECK(info.is_nullable);
+
+    const auto& nullable_column = get_nullable_column();
+    const auto get_nested_column = [&]() -> ColumnPtr {
+        const auto& nested_column = nullable_column.get_nested_column_ptr();
+        if (info.is_const) {
+            return ColumnConst::create(nested_column, column->size());
+        }
+        return nested_column;
+    };
+
+    auto nested_type = assert_cast<const DataTypeNullable*, 
TypeCheckOnRelease::DISABLE>(type.get())
+                               ->get_nested_type();
+    if (replace_null_data_to_default && info.has_null) {
+        if (column->try_replace_null_payload_with_default_without_cow()) {
+            return {get_nested_column(), nested_type, ""};
+        }
+
+        // Only copy the nested column because the original nullable column 
must remain unchanged.
+        const auto nested_column = get_nested_column();
+        auto mutable_nested_col = 
nested_column->clone_resized(nested_column->size());
+        
mutable_nested_col->replace_column_null_data(nullable_column.get_null_map_data().data());
+        return {std::move(mutable_nested_col), nested_type, ""};
+    }
+    return {get_nested_column(), nested_type, ""};
 }
 
 Status ColumnWithTypeAndName::check_type_and_column_match() const {
diff --git a/be/src/core/block/column_with_type_and_name.h 
b/be/src/core/block/column_with_type_and_name.h
index 9b1c357ac66..d6a1954101b 100644
--- a/be/src/core/block/column_with_type_and_name.h
+++ b/be/src/core/block/column_with_type_and_name.h
@@ -26,17 +26,29 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
+#include "core/column/column_vector.h"
 #include "core/data_type/data_type.h"
 #include "core/data_type_serde/data_type_serde.h"
 #include "core/types.h"
 
 namespace doris {
+class ColumnNullable;
 class PColumnMeta;
 } // namespace doris
 
 namespace doris {
 
+struct NullableColumnInfo {
+    bool has_null = false;
+    bool only_null = false;
+    bool is_const = false;
+    bool is_nullable = false;
+};
+
+using NullableColumnInfos = std::vector<NullableColumnInfo>;
+
 // class WriteBuffer;
 
 /** Column data along with its data type and name.
@@ -69,9 +81,15 @@ struct ColumnWithTypeAndName {
 
     void to_pb_column_meta(PColumnMeta* col_meta) const;
 
-    ColumnWithTypeAndName unnest_nullable(bool replace_null_data_to_default = 
false) const;
+    const ColumnUInt8::Ptr& get_nullable_null_map_column() const;
+    NullableColumnInfo get_nullable_column_info() const;
+    ColumnWithTypeAndName unnest_nullable(const NullableColumnInfo& info,
+                                          bool replace_null_data_to_default) 
const;
 
     Status check_type_and_column_match() const;
+
+private:
+    const ColumnNullable& get_nullable_column() const;
 };
 
 } // namespace doris
diff --git a/be/src/core/column/column.h b/be/src/core/column/column.h
index d09263a9ae6..9a6f3d05d02 100644
--- a/be/src/core/column/column.h
+++ b/be/src/core/column/column.h
@@ -764,6 +764,17 @@ public:
     // column_vector and column_decimal override this method to return true
     virtual bool support_replace_column_null_data() const { return false; }
 
+    /**
+     * Try to replace the payload of NULL rows with the nested column's 
default value without
+     * going through COW. Implementations must return false without modifying 
data unless the
+     * complete column ownership chain is exclusive. This is only safe because 
payloads of rows
+     * that are already NULL are not observable through the nullable column. 
In particular, a
+     * shared nested column may belong to another nullable column with a 
different null map.
+     *
+     * This bypasses the normal COW mutation path. Do not use it for general 
column mutation.
+     */
+    virtual bool try_replace_null_payload_with_default_without_cow() const { 
return false; }
+
     // For float/double types, replace -0.0 with 0.0, set NaN to quiet NaN,
     // used to ensure data hash equality for -0.0 and +0.0, e.g. aggregate and 
join
     virtual void replace_float_special_values() {}
diff --git a/be/src/core/column/column_const.h 
b/be/src/core/column/column_const.h
index f4a373f3178..7c38fd2cbbe 100644
--- a/be/src/core/column/column_const.h
+++ b/be/src/core/column/column_const.h
@@ -310,6 +310,13 @@ public:
         return data->support_replace_column_null_data();
     }
 
+    bool try_replace_null_payload_with_default_without_cow() const override {
+        if (!IColumn::is_exclusive()) {
+            return false;
+        }
+        return data->try_replace_null_payload_with_default_without_cow();
+    }
+
     void finalize() override { data->finalize(); }
 
     void erase(size_t start, size_t length) override {
diff --git a/be/src/core/column/column_nullable.cpp 
b/be/src/core/column/column_nullable.cpp
index ed51d4e7a42..a3857c2af04 100644
--- a/be/src/core/column/column_nullable.cpp
+++ b/be/src/core/column/column_nullable.cpp
@@ -676,6 +676,29 @@ bool ColumnNullable::only_null() const {
     return !simd::contain_zero(get_null_map_data().data(), size());
 }
 
+ColumnNullable::NullMapState ColumnNullable::get_null_map_state() const {
+    const auto& null_map = get_null_map_data();
+    if (null_map.empty()) {
+        return {.has_null = false, .only_null = true};
+    }
+
+    if (null_map[0]) {
+        return {.has_null = true,
+                .only_null = !simd::contain_zero(null_map.data() + 1, 
null_map.size() - 1)};
+    }
+    return {.has_null = simd::contain_one(null_map.data() + 1, null_map.size() 
- 1),
+            .only_null = false};
+}
+
+bool ColumnNullable::try_replace_null_payload_with_default_without_cow() const 
{
+    if (!is_exclusive()) {
+        return false;
+    }
+
+    
const_cast<IColumn&>(get_nested_column()).replace_column_null_data(get_null_map_data().data());
+    return true;
+}
+
 bool ColumnNullable::has_null(size_t begin, size_t end) const {
     return simd::contain_one(get_null_map_data().data() + begin, end - begin);
 }
diff --git a/be/src/core/column/column_nullable.h 
b/be/src/core/column/column_nullable.h
index de0e6b64e5c..c4ec70fb69a 100644
--- a/be/src/core/column/column_nullable.h
+++ b/be/src/core/column/column_nullable.h
@@ -62,6 +62,11 @@ private:
     ColumnNullable(const ColumnNullable&) = default;
 
 public:
+    struct NullMapState {
+        bool has_null;
+        bool only_null;
+    };
+
     /** Create a column from immutable/shared subcolumns without cloning them.
       * Call IColumn::mutate before modifying the returned column tree.
       */
@@ -275,7 +280,10 @@ public:
                get_null_map_column().is_exclusive();
     }
 
+    bool try_replace_null_payload_with_default_without_cow() const override;
+
     bool only_null() const override;
+    NullMapState get_null_map_state() const;
 
     // used in schema change
     void change_nested_column(ColumnPtr& other) { ((ColumnPtr&)_nested_column) 
= other; }
diff --git a/be/src/exprs/function/cast/function_cast.cpp 
b/be/src/exprs/function/cast/function_cast.cpp
index febf94eb336..5561a3a76de 100644
--- a/be/src/exprs/function/cast/function_cast.cpp
+++ b/be/src/exprs/function/cast/function_cast.cpp
@@ -195,24 +195,37 @@ WrapperType prepare_remove_nullable(FunctionContext* 
context, const DataTypePtr&
             bool replace_null_data_to_default = 
need_replace_null_data_to_default(
                     context, from_type_not_nullable, to_type_not_nullable);
 
+            NullableColumnInfo source_info;
+            if (block.get_by_position(arguments[0]).type->is_nullable()) {
+                source_info = 
block.get_by_position(arguments[0]).get_nullable_column_info();
+            }
             auto nested_result_index = block.columns();
-            block.insert(block.get_by_position(result).unnest_nullable());
+            const auto& result_column = block.get_by_position(result);
+            block.insert({nullptr, to_type_not_nullable, result_column.name});
             auto nested_source_index = block.columns();
-            block.insert(block.get_by_position(arguments[0])
-                                 
.unnest_nullable(replace_null_data_to_default));
+            if (source_info.is_nullable) {
+                block.insert(block.get_by_position(arguments[0])
+                                     .unnest_nullable(source_info, 
replace_null_data_to_default));
+            } else {
+                block.insert(block.get_by_position(arguments[0]));
+            }
 
-            const auto& arg_col = block.get_by_position(arguments[0]);
             const NullMap::value_type* arg_null_map = nullptr;
-            if (const auto* nullable = 
check_and_get_column<ColumnNullable>(*arg_col.column)) {
-                arg_null_map = nullable->get_null_map_data().data();
+            if (source_info.is_nullable) {
+                arg_null_map = block.get_by_position(arguments[0])
+                                       .get_nullable_null_map_column()
+                                       ->get_data()
+                                       .data();
             }
             RETURN_IF_ERROR(prepare_impl(context, from_type_not_nullable, 
to_type_not_nullable)(
                     context, block, {nested_source_index}, 
nested_result_index, input_rows_count,
                     arg_null_map));
 
+            NullableColumnInfos nullable_column_infos(block.columns());
+            nullable_column_infos[arguments[0]] = std::move(source_info);
             block.get_by_position(result).column =
                     
wrap_in_nullable(block.get_by_position(nested_result_index).column, block,
-                                     arguments, input_rows_count);
+                                     arguments, nullable_column_infos, 
input_rows_count);
 
             block.erase(nested_source_index);
             block.erase(nested_result_index);
diff --git a/be/src/exprs/function/function.cpp 
b/be/src/exprs/function/function.cpp
index 1d2b9347a52..4f8c4e25059 100644
--- a/be/src/exprs/function/function.cpp
+++ b/be/src/exprs/function/function.cpp
@@ -43,6 +43,7 @@
 
 namespace doris {
 ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const 
ColumnNumbers& args,
+                           const NullableColumnInfos& nullable_column_infos,
                            size_t input_rows_count) {
     ColumnPtr result_null_map_column;
     /// If result is already nullable.
@@ -55,13 +56,13 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const 
Block& block, const Colum
     }
 
     for (const auto& arg : args) {
-        const ColumnWithTypeAndName& elem = block.get_by_position(arg);
-        if (!elem.type->is_nullable() || is_column_const(*elem.column)) {
+        const auto& info = nullable_column_infos[arg];
+        if (!info.is_nullable || info.is_const) {
             continue;
         }
 
-        if (auto nullable = cast_to_column<ColumnNullable>(elem.column); 
nullable->has_null()) {
-            const ColumnPtr& null_map_column = 
nullable->get_null_map_column_ptr();
+        if (info.has_null) {
+            const auto& null_map_column = 
block.get_by_position(arg).get_nullable_null_map_column();
             if (!result_null_map_column) { // NOLINT(bugprone-use-after-move)
                 result_null_map_column = null_map_column;
                 continue;
@@ -73,8 +74,7 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& 
block, const Colum
 
             NullMap& result_null_map =
                     
assert_cast<ColumnUInt8&>(*mutable_result_null_map_column).get_data();
-            const NullMap& src_null_map =
-                    assert_cast<const 
ColumnUInt8&>(*null_map_column).get_data();
+            const NullMap& src_null_map = null_map_column->get_data();
 
             VectorizedUtils::update_null_map(result_null_map, src_null_map);
         }
@@ -99,6 +99,18 @@ ColumnPtr wrap_in_nullable(const ColumnPtr& src, const 
Block& block, const Colum
     return ColumnNullable::create(src_not_nullable, result_null_map_column);
 }
 
+ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const 
ColumnNumbers& args,
+                           size_t input_rows_count) {
+    NullableColumnInfos nullable_column_infos(block.columns());
+    for (const auto arg : args) {
+        const auto& column = block.get_by_position(arg);
+        if (column.type->is_nullable()) {
+            nullable_column_infos[arg] = column.get_nullable_column_info();
+        }
+    }
+    return wrap_in_nullable(src, block, args, nullable_column_infos, 
input_rows_count);
+}
+
 bool have_null_column(const Block& block, const ColumnNumbers& args) {
     return std::ranges::any_of(args, [&block](const auto& elem) {
         return block.get_by_position(elem).type->is_nullable();
@@ -195,16 +207,25 @@ Status 
PreparedFunctionImpl::default_implementation_for_nulls(
         return Status::OK();
     }
 
-    if (std::ranges::any_of(args, [&block](const auto& elem) {
-            return block.get_by_position(elem).column->only_null();
-        })) {
-        block.get_by_position(result).column =
-                
block.get_by_position(result).type->create_column_const(input_rows_count, 
Field());
-        *executed = true;
-        return Status::OK();
-    }
-
     if (have_null_column(block, args)) {
+        NullableColumnInfos nullable_column_infos(block.columns());
+        for (const auto arg : args) {
+            const auto& argument = block.get_by_position(arg);
+            if (!argument.type->is_nullable()) {
+                continue;
+            }
+
+            auto info = argument.get_nullable_column_info();
+            if (info.only_null) {
+                auto& result_column = block.get_by_position(result);
+                result_column.column =
+                        
result_column.type->create_column_const(input_rows_count, Field());
+                *executed = true;
+                return Status::OK();
+            }
+            nullable_column_infos[arg] = info;
+        }
+
         bool need_to_default = need_replace_null_data_to_default();
         // extract nested column from nulls
         ColumnNumbers new_args;
@@ -213,7 +234,8 @@ Status 
PreparedFunctionImpl::default_implementation_for_nulls(
         for (int i = 0; i < args.size(); ++i) {
             uint32_t arg = args[i];
             new_args.push_back(i);
-            
new_block.insert(block.get_by_position(arg).unnest_nullable(need_to_default));
+            
new_block.insert(block.get_by_position(arg).unnest_nullable(nullable_column_infos[arg],
+                                                                        
need_to_default));
         }
         new_block.insert(block.get_by_position(result));
         int new_result = new_block.columns() - 1;
@@ -222,8 +244,9 @@ Status 
PreparedFunctionImpl::default_implementation_for_nulls(
         // After run with nested, wrap them in null. Before this, 
block.get_by_position(result).type
         // is not compatible with get_by_position(result).column
 
-        block.get_by_position(result).column = wrap_in_nullable(
-                new_block.get_by_position(new_result).column, block, args, 
input_rows_count);
+        block.get_by_position(result).column =
+                wrap_in_nullable(new_block.get_by_position(new_result).column, 
block, args,
+                                 nullable_column_infos, input_rows_count);
 
         *executed = true;
         return Status::OK();
diff --git a/be/src/exprs/function/function.h b/be/src/exprs/function/function.h
index d26cab4d783..8307a6a1c7c 100644
--- a/be/src/exprs/function/function.h
+++ b/be/src/exprs/function/function.h
@@ -681,5 +681,8 @@ using FunctionPtr = std::shared_ptr<IFunction>;
   */
 ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const 
ColumnNumbers& args,
                            size_t input_rows_count);
+ColumnPtr wrap_in_nullable(const ColumnPtr& src, const Block& block, const 
ColumnNumbers& args,
+                           const NullableColumnInfos& nullable_column_infos,
+                           size_t input_rows_count);
 
 } // namespace doris
diff --git a/be/test/core/block/column_with_type_and_name_test.cpp 
b/be/test/core/block/column_with_type_and_name_test.cpp
index 8a5fd999d42..bafce0c4350 100644
--- a/be/test/core/block/column_with_type_and_name_test.cpp
+++ b/be/test/core/block/column_with_type_and_name_test.cpp
@@ -35,9 +35,149 @@ TEST(ColumnWithTypeAndNameTest, get_nested_test) {
     column_with_type_and_name.type =
             
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
     column_with_type_and_name.name = "column_with_type_and_name";
-    auto result = column_with_type_and_name.unnest_nullable(true);
+    auto result = column_with_type_and_name.unnest_nullable(
+            column_with_type_and_name.get_nullable_column_info(), true);
     EXPECT_TRUE(is_column_const(*result.column));
     EXPECT_EQ(result.column->size(), 3);
+    EXPECT_EQ(result.column->get_int(0), 0);
+}
+
+TEST(ColumnWithTypeAndNameTest, get_nullable_column_info_for_const_column) {
+    auto nullable_type = 
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
+
+    auto null_column = 
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+                                              
ColumnHelper::create_column<DataTypeUInt8>({true}));
+    ColumnWithTypeAndName const_null 
{ColumnConst::create(std::move(null_column), 3), nullable_type,
+                                      "const_null"};
+    auto null_info = const_null.get_nullable_column_info();
+    EXPECT_TRUE(null_info.is_const);
+    EXPECT_TRUE(null_info.has_null);
+    EXPECT_TRUE(null_info.only_null);
+    EXPECT_EQ(const_null.get_nullable_null_map_column()->size(), 1);
+
+    auto non_null_column =
+            
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+                                   
ColumnHelper::create_column<DataTypeUInt8>({false}));
+    ColumnWithTypeAndName const_non_null 
{ColumnConst::create(std::move(non_null_column), 3),
+                                          nullable_type, "const_non_null"};
+    auto non_null_info = const_non_null.get_nullable_column_info();
+    EXPECT_TRUE(non_null_info.is_const);
+    EXPECT_FALSE(non_null_info.has_null);
+    EXPECT_FALSE(non_null_info.only_null);
+    EXPECT_EQ(const_non_null.get_nullable_null_map_column()->size(), 1);
+}
+
+TEST(ColumnWithTypeAndNameTest, get_nullable_column_info_null_map_states) {
+    auto nullable_type = 
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>());
+
+    const auto check_state = [&](std::initializer_list<int32_t> values,
+                                 std::initializer_list<uint8_t> null_map, bool 
has_null,
+                                 bool only_null) {
+        ColumnWithTypeAndName column {
+                
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>(values),
+                                       
ColumnHelper::create_column<DataTypeUInt8>(null_map)),
+                nullable_type, "nullable"};
+        const auto info = column.get_nullable_column_info();
+        EXPECT_EQ(info.has_null, has_null);
+        EXPECT_EQ(info.only_null, only_null);
+    };
+
+    check_state({}, {}, false, true);
+    check_state({1, 2, 3}, {false, false, false}, false, false);
+    check_state({1, 2, 3}, {true, true, true}, true, true);
+    check_state({1, 2, 3}, {false, true, false}, true, false);
+    check_state({1, 2, 3}, {true, false, true}, true, false);
+}
+
+TEST(ColumnWithTypeAndNameTest, 
unnest_nullable_without_null_reuses_nested_column) {
+    auto nested_column = ColumnHelper::create_column<DataTypeInt32>({1, 2, 3});
+    auto nullable_column = ColumnNullable::create(
+            nested_column, ColumnHelper::create_column<DataTypeUInt8>({false, 
false, false}));
+    ColumnWithTypeAndName column_with_type_and_name {
+            std::move(nullable_column),
+            
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), 
"nullable"};
+
+    auto result = column_with_type_and_name.unnest_nullable(
+            column_with_type_and_name.get_nullable_column_info(), true);
+
+    EXPECT_EQ(result.column.get(), nested_column.get());
+}
+
+TEST(ColumnWithTypeAndNameTest, 
unnest_nullable_with_unique_nested_replaces_data_in_place) {
+    auto nullable_column = ColumnNullable::create(
+            ColumnHelper::create_column<DataTypeInt32>({1, 2, 3}),
+            ColumnHelper::create_column<DataTypeUInt8>({false, true, false}));
+    const auto* original_nested_column =
+            static_cast<const 
ColumnNullable&>(*nullable_column).get_nested_column_ptr().get();
+    ColumnWithTypeAndName column_with_type_and_name {
+            std::move(nullable_column),
+            
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), 
"nullable"};
+
+    const auto info = column_with_type_and_name.get_nullable_column_info();
+    auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+    EXPECT_EQ(result.column.get(), original_nested_column);
+    EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1], 
0);
+}
+
+TEST(ColumnWithTypeAndNameTest, 
unnest_nullable_with_shared_nested_preserves_visible_alias) {
+    auto nested_column = ColumnHelper::create_column<DataTypeInt32>({1, 2, 3});
+    auto nullable_column = ColumnNullable::create(
+            nested_column, ColumnHelper::create_column<DataTypeUInt8>({false, 
true, false}));
+    auto visible_alias = ColumnNullable::create(
+            nested_column, ColumnHelper::create_column<DataTypeUInt8>({false, 
false, false}));
+    ColumnWithTypeAndName column_with_type_and_name {
+            std::move(nullable_column),
+            
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), 
"nullable"};
+
+    const auto info = column_with_type_and_name.get_nullable_column_info();
+    auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+    EXPECT_NE(result.column.get(), nested_column.get());
+    EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1], 
0);
+    EXPECT_FALSE(visible_alias->is_null_at(1));
+    const ColumnNullable& visible_alias_column = *visible_alias;
+    EXPECT_EQ(
+            assert_cast<const 
ColumnInt32&>(visible_alias_column.get_nested_column()).get_data()[1],
+            2);
+}
+
+TEST(ColumnWithTypeAndNameTest, 
unnest_nullable_with_shared_source_replaces_data_on_copy) {
+    auto nullable_column = ColumnNullable::create(
+            ColumnHelper::create_column<DataTypeInt32>({1, 2, 3}),
+            ColumnHelper::create_column<DataTypeUInt8>({false, true, false}));
+    ColumnWithTypeAndName column_with_type_and_name {
+            std::move(nullable_column),
+            
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), 
"nullable"};
+    ColumnPtr source_alias = column_with_type_and_name.column;
+    const auto& original_nested_column =
+            assert_cast<const 
ColumnNullable&>(*source_alias).get_nested_column();
+
+    const auto info = column_with_type_and_name.get_nullable_column_info();
+    auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+    EXPECT_NE(result.column.get(), &original_nested_column);
+    EXPECT_EQ(assert_cast<const ColumnInt32&>(*result.column).get_data()[1], 
0);
+    EXPECT_EQ(assert_cast<const 
ColumnInt32&>(original_nested_column).get_data()[1], 2);
+}
+
+TEST(ColumnWithTypeAndNameTest, 
unnest_const_nullable_with_shared_source_replaces_data_on_copy) {
+    auto nullable_column =
+            
ColumnNullable::create(ColumnHelper::create_column<DataTypeInt32>({1}),
+                                   
ColumnHelper::create_column<DataTypeUInt8>({true}));
+    ColumnWithTypeAndName column_with_type_and_name {
+            ColumnConst::create(std::move(nullable_column), 3),
+            
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeInt32>()), 
"nullable"};
+    ColumnPtr source_alias = column_with_type_and_name.column;
+    const auto& original_nullable_column = assert_cast<const ColumnNullable&>(
+            assert_cast<const ColumnConst&>(*source_alias).get_data_column());
+
+    const auto info = column_with_type_and_name.get_nullable_column_info();
+    auto result = column_with_type_and_name.unnest_nullable(info, true);
+
+    EXPECT_TRUE(is_column_const(*result.column));
+    EXPECT_EQ(result.column->get_int(0), 0);
+    EXPECT_EQ(original_nullable_column.get_nested_column().get_int(0), 1);
 }
 
 } // namespace doris
diff --git a/be/test/exprs/function/function_arithmetic_test.cpp 
b/be/test/exprs/function/function_arithmetic_test.cpp
index 09c66ba9bf8..4d3829bf30a 100644
--- a/be/test/exprs/function/function_arithmetic_test.cpp
+++ b/be/test/exprs/function/function_arithmetic_test.cpp
@@ -32,6 +32,14 @@
 
 namespace doris {
 
+TEST(function_arithmetic_test, add_mixed_nullable_arguments_test) {
+    InputTypeSet input_types = {Nullable {PrimitiveType::TYPE_INT},
+                                Notnull {PrimitiveType::TYPE_INT}};
+    DataSet data_set = {{{int32_t {1}, int32_t {2}}, int32_t {3}}, {{Null(), 
int32_t {4}}, Null()}};
+
+    static_cast<void>(check_function<DataTypeInt32, true>("add", input_types, 
data_set));
+}
+
 TEST(function_arithmetic_test, function_arithmetic_mod_test) {
     std::string func_name = "mod";
 


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

Reply via email to