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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 874089ab056 [opt](be) Backport nullable and projection optimizations 
to branch-4.1 (#66586)
874089ab056 is described below

commit 874089ab056b597b1e2b4c17a7cccc9433f7d4b9
Author: Mryange <[email protected]>
AuthorDate: Tue Aug 11 09:47:07 2026 +0800

    [opt](be) Backport nullable and projection optimizations to branch-4.1 
(#66586)
    
    Related PR: #66031, #66085
---
 be/src/core/block/column_with_type_and_name.cpp    |  88 ++++++++-----
 be/src/core/block/column_with_type_and_name.h      |  19 +++
 be/src/core/column/column.h                        |  11 ++
 be/src/core/column/column_const.h                  |   9 ++
 be/src/core/column/column_nullable.cpp             |  23 ++++
 be/src/core/column/column_nullable.h               |   8 ++
 be/src/core/column/column_variant.cpp              |  14 ++
 be/src/core/column/column_variant.h                |   2 +
 be/src/exec/operator/operator.cpp                  |  80 ++++++------
 be/src/exec/operator/operator.h                    |   4 -
 be/src/exec/scan/scanner.cpp                       |  62 +++++----
 be/src/exprs/function/cast/function_cast.cpp       |  27 +++-
 be/src/exprs/function/function.cpp                 |  60 ++++++---
 be/src/exprs/function/function.h                   |   3 +
 .../core/block/column_with_type_and_name_test.cpp  | 142 ++++++++++++++++++++-
 be/test/core/column/column_const_test.cpp          |  11 +-
 be/test/core/column/column_nullable_test.cpp       |   7 +
 be/test/core/column/column_variant_test.cpp        |  38 ++++--
 be/test/exec/operator/operator_projection_test.cpp |  81 ++++++++++++
 be/test/exec/scan/scanner_late_arrival_rf_test.cpp |  45 +++++++
 .../exprs/function/function_arithmetic_test.cpp    |   8 ++
 21 files changed, 604 insertions(+), 138 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..028f91fabb0 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,67 @@ 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 {
+    NullableColumnInfo info;
     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 {
+        info = get_nullable_column_info();
+    }
+    return unnest_nullable(info, replace_null_data_to_default);
+}
+
+ColumnWithTypeAndName ColumnWithTypeAndName::unnest_nullable(
+        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..577c8edf623 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,16 @@ struct ColumnWithTypeAndName {
 
     void to_pb_column_meta(PColumnMeta* col_meta) const;
 
+    const ColumnUInt8::Ptr& get_nullable_null_map_column() const;
+    NullableColumnInfo get_nullable_column_info() const;
     ColumnWithTypeAndName unnest_nullable(bool replace_null_data_to_default = 
false) 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 3b727285e64..aa29ca601e7 100644
--- a/be/src/core/column/column.h
+++ b/be/src/core/column/column.h
@@ -724,6 +724,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 dd5296cc1ef..9a79d8d0cb5 100644
--- a/be/src/core/column/column_const.h
+++ b/be/src/core/column/column_const.h
@@ -121,6 +121,8 @@ public:
 
     bool is_variable_length() const override { return 
data->is_variable_length(); }
 
+    bool is_exclusive() const override { return IColumn::is_exclusive() && 
data->is_exclusive(); }
+
     std::string get_name() const override { return "Const(" + data->get_name() 
+ ")"; }
 
     void resize(size_t new_size) override { s = new_size; }
@@ -307,6 +309,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 01d0fc072ed..c92ab615380 100644
--- a/be/src/core/column/column_nullable.cpp
+++ b/be/src/core/column/column_nullable.cpp
@@ -699,6 +699,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 3e7afb904e6..2c611e27324 100644
--- a/be/src/core/column/column_nullable.h
+++ b/be/src/core/column/column_nullable.h
@@ -64,6 +64,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.
       */
@@ -270,7 +275,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/core/column/column_variant.cpp 
b/be/src/core/column/column_variant.cpp
index 1efeb159b2a..820fbe2794f 100644
--- a/be/src/core/column/column_variant.cpp
+++ b/be/src/core/column/column_variant.cpp
@@ -832,6 +832,20 @@ size_t ColumnVariant::allocated_bytes() const {
     return res;
 }
 
+bool ColumnVariant::is_exclusive() const {
+    if (!IColumn::is_exclusive()) {
+        return false;
+    }
+    for (const auto& entry : subcolumns) {
+        for (const auto& part : entry->data.data) {
+            if (!part->is_exclusive()) {
+                return false;
+            }
+        }
+    }
+    return serialized_sparse_column->is_exclusive() && 
serialized_doc_value_column->is_exclusive();
+}
+
 void ColumnVariant::for_each_subcolumn(ColumnCallback callback) {
     for (auto& entry : subcolumns) {
         for (auto& part : entry->data.data) {
diff --git a/be/src/core/column/column_variant.h 
b/be/src/core/column/column_variant.h
index 1d5c4eed137..070ddc957f7 100644
--- a/be/src/core/column/column_variant.h
+++ b/be/src/core/column/column_variant.h
@@ -469,6 +469,8 @@ public:
 
     bool has_enough_capacity(const IColumn& src) const override { return 
false; }
 
+    bool is_exclusive() const override;
+
     void for_each_subcolumn(ColumnCallback callback) override;
 
     // Do nothing, call try_insert instead
diff --git a/be/src/exec/operator/operator.cpp 
b/be/src/exec/operator/operator.cpp
index a7325981fb3..1c072d9565c 100644
--- a/be/src/exec/operator/operator.cpp
+++ b/be/src/exec/operator/operator.cpp
@@ -333,58 +333,54 @@ Status OperatorXBase::do_projections(RuntimeState* state, 
Block* origin_block,
     if (rows == 0) {
         return Status::OK();
     }
-    Block input_block = *origin_block;
-
-    size_t bytes_usage = 0;
-    ColumnsWithTypeAndName new_columns;
-    for (const auto& projections : local_state->_intermediate_projections) {
-        new_columns.resize(projections.size());
-        for (int i = 0; i < projections.size(); i++) {
-            RETURN_IF_ERROR(projections[i]->execute(&input_block, 
new_columns[i]));
-        }
-        Block tmp_block {new_columns};
-        bytes_usage += tmp_block.allocated_bytes();
-        input_block.swap(tmp_block);
-    }
-
-    DCHECK_EQ(rows, input_block.rows());
-    auto insert_column_datas = [&](auto& to, ColumnPtr& from, size_t rows) {
-        if (to->is_nullable() && !from->is_nullable()) {
-            if (_keep_origin || !from->is_exclusive()) {
-                auto& null_column = reinterpret_cast<ColumnNullable&>(*to);
-                null_column.get_nested_column().insert_range_from(*from, 0, 
rows);
-                null_column.get_null_map_column().get_data().resize_fill(rows, 
0);
-                bytes_usage += null_column.allocated_bytes();
-            } else {
-                to = ColumnNullable::create(IColumn::mutate(std::move(from)),
-                                            ColumnUInt8::create(rows, 0));
-            }
-        } else {
-            if (_keep_origin || !from->is_exclusive()) {
-                to->insert_range_from(*from, 0, rows);
-                bytes_usage += from->allocated_bytes();
-            } else {
-                to = IColumn::mutate(std::move(from));
+    SCOPED_PEAK_MEM(&local_state->_estimate_memory_usage);
+
+    {
+        Block input_block = *origin_block;
+
+        ColumnsWithTypeAndName new_columns;
+        for (const auto& projections : local_state->_intermediate_projections) 
{
+            new_columns.resize(projections.size());
+            for (int i = 0; i < projections.size(); i++) {
+                RETURN_IF_ERROR(projections[i]->execute(&input_block, 
new_columns[i]));
             }
+            Block tmp_block {new_columns};
+            input_block.swap(tmp_block);
         }
-    };
 
-    auto scoped_mutable_block = 
VectorizedUtils::build_scoped_mutable_mem_reuse_block(
-            output_block, *_output_row_descriptor);
-    auto& mutable_block = scoped_mutable_block.mutable_block();
-    auto& mutable_columns = mutable_block.mutable_columns();
-    if (rows != 0) {
+        DCHECK_EQ(rows, input_block.rows());
+
+        auto scoped_mutable_block = 
VectorizedUtils::build_scoped_mutable_mem_reuse_block(
+                output_block, *_output_row_descriptor);
+        auto& mutable_columns = scoped_mutable_block.mutable_columns();
         DCHECK_EQ(mutable_columns.size(), local_state->_projections.size()) << 
debug_string();
+        Columns shared_columns(mutable_columns.size());
+
         for (int i = 0; i < mutable_columns.size(); ++i) {
             ColumnPtr column_ptr;
             
RETURN_IF_ERROR(local_state->_projections[i]->execute(&input_block, 
column_ptr));
             column_ptr = column_ptr->convert_to_full_column_if_const();
-            bytes_usage += column_ptr->allocated_bytes();
-            insert_column_datas(mutable_columns[i], column_ptr, rows);
+            if (is_column_nullable(*mutable_columns[i]) && 
!is_column_nullable(*column_ptr)) {
+                column_ptr = make_nullable(column_ptr, false);
+            }
+            if (column_ptr->is_exclusive()) {
+                mutable_columns[i] = IColumn::mutate(std::move(column_ptr));
+            } else {
+                shared_columns[i] = std::move(column_ptr);
+            }
+        }
+
+        scoped_mutable_block.restore();
+        for (int i = 0; i < shared_columns.size(); ++i) {
+            if (shared_columns[i]) {
+                output_block->replace_by_position(i, 
std::move(shared_columns[i]));
+            }
         }
-        DCHECK(mutable_block.rows() == rows);
     }
-    local_state->_estimate_memory_usage += bytes_usage;
+
+    origin_block->clear_column_data(
+            
local_state->_parent->intermediate_row_desc().num_materialized_slots());
+    DCHECK_EQ(output_block->rows(), rows);
 
     return Status::OK();
 }
diff --git a/be/src/exec/operator/operator.h b/be/src/exec/operator/operator.h
index 676a8b67b18..e193d57163b 100644
--- a/be/src/exec/operator/operator.h
+++ b/be/src/exec/operator/operator.h
@@ -1020,10 +1020,6 @@ protected:
     std::string _op_name;
     int _parallel_tasks = 0;
 
-    //_keep_origin is used to avoid copying during projection,
-    // currently set to false only in the nestloop join.
-    bool _keep_origin = true;
-
     // _blockable is true if the operator contains expressions that may block 
execution
     bool _blockable = false;
 };
diff --git a/be/src/exec/scan/scanner.cpp b/be/src/exec/scan/scanner.cpp
index 41a1e0328ac..39e22312e75 100644
--- a/be/src/exec/scan/scanner.cpp
+++ b/be/src/exec/scan/scanner.cpp
@@ -196,37 +196,51 @@ Status Scanner::_do_projections(Block* origin_block, 
Block* output_block) {
     if (rows == 0) {
         return Status::OK();
     }
-    Block input_block = *origin_block;
 
-    std::vector<int> result_column_ids;
-    for (auto& projections : _intermediate_projections) {
-        result_column_ids.resize(projections.size());
-        for (int i = 0; i < projections.size(); i++) {
-            RETURN_IF_ERROR(projections[i]->execute(&input_block, 
&result_column_ids[i]));
-        }
-        input_block.shuffle_columns(result_column_ids);
-    }
-
-    DCHECK_EQ(rows, input_block.rows());
-    auto scoped_mutable_block = 
VectorizedUtils::build_scoped_mutable_mem_reuse_block(
-            output_block, *_output_row_descriptor);
-    auto& mutable_block = scoped_mutable_block.mutable_block();
+    {
+        Block input_block = *origin_block;
 
-    auto& mutable_columns = mutable_block.mutable_columns();
+        std::vector<int> result_column_ids;
+        for (auto& projections : _intermediate_projections) {
+            result_column_ids.resize(projections.size());
+            for (int i = 0; i < projections.size(); i++) {
+                RETURN_IF_ERROR(projections[i]->execute(&input_block, 
&result_column_ids[i]));
+            }
+            input_block.shuffle_columns(result_column_ids);
+        }
 
-    DCHECK_EQ(mutable_columns.size(), _projections.size());
+        DCHECK_EQ(rows, input_block.rows());
+        auto scoped_mutable_block = 
VectorizedUtils::build_scoped_mutable_mem_reuse_block(
+                output_block, *_output_row_descriptor);
+        auto& mutable_columns = scoped_mutable_block.mutable_columns();
+        DCHECK_EQ(mutable_columns.size(), _projections.size());
+        Columns shared_columns(mutable_columns.size());
+
+        for (int i = 0; i < mutable_columns.size(); ++i) {
+            ColumnPtr column_ptr;
+            RETURN_IF_ERROR(_projections[i]->execute(&input_block, 
column_ptr));
+            column_ptr = column_ptr->convert_to_full_column_if_const();
+            if (mutable_columns[i]->is_nullable() != 
column_ptr->is_nullable()) {
+                throw Exception(ErrorCode::INTERNAL_ERROR, "Nullable 
mismatch");
+            }
+            if (column_ptr->is_exclusive()) {
+                mutable_columns[i] = IColumn::mutate(std::move(column_ptr));
+            } else {
+                shared_columns[i] = std::move(column_ptr);
+            }
+        }
 
-    for (int i = 0; i < mutable_columns.size(); ++i) {
-        ColumnPtr column_ptr;
-        RETURN_IF_ERROR(_projections[i]->execute(&input_block, column_ptr));
-        column_ptr = column_ptr->convert_to_full_column_if_const();
-        if (mutable_columns[i]->is_nullable() != column_ptr->is_nullable()) {
-            throw Exception(ErrorCode::INTERNAL_ERROR, "Nullable mismatch");
+        scoped_mutable_block.restore();
+        for (int i = 0; i < shared_columns.size(); ++i) {
+            if (shared_columns[i]) {
+                output_block->replace_by_position(i, 
std::move(shared_columns[i]));
+            }
         }
-        mutable_columns[i] = IColumn::mutate(std::move(column_ptr));
     }
 
-    scoped_mutable_block.restore();
+    origin_block->clear_column_data(
+            _local_state->_parent->row_descriptor().num_materialized_slots());
+    DCHECK_EQ(output_block->rows(), rows);
 
     return Status::OK();
 }
diff --git a/be/src/exprs/function/cast/function_cast.cpp 
b/be/src/exprs/function/cast/function_cast.cpp
index 99b60627d1b..75879ebf6b3 100644
--- a/be/src/exprs/function/cast/function_cast.cpp
+++ b/be/src/exprs/function/cast/function_cast.cpp
@@ -182,24 +182,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 c7b35e8260f..552fafdb459 100644
--- a/be/src/exprs/function/function.cpp
+++ b/be/src/exprs/function/function.cpp
@@ -44,6 +44,7 @@
 namespace doris {
 #include "common/compile_check_begin.h"
 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.
@@ -56,14 +57,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 (const auto* nullable = assert_cast<const 
ColumnNullable*>(elem.column.get());
-            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->clone_resized(input_rows_count);
                 continue;
@@ -75,8 +75,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);
         }
@@ -101,6 +100,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();
@@ -197,16 +208,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;
@@ -215,7 +235,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.simple_insert(block.get_by_position(arg).unnest_nullable(need_to_default));
+            new_block.simple_insert(block.get_by_position(arg).unnest_nullable(
+                    nullable_column_infos[arg], need_to_default));
         }
         new_block.simple_insert(block.get_by_position(result));
         int new_result = new_block.columns() - 1;
@@ -224,8 +245,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 13d4bfd6bb5..807b01b0040 100644
--- a/be/src/exprs/function/function.h
+++ b/be/src/exprs/function/function.h
@@ -680,5 +680,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/core/column/column_const_test.cpp 
b/be/test/core/column/column_const_test.cpp
index e9f57df213b..cc9980c654d 100644
--- a/be/test/core/column/column_const_test.cpp
+++ b/be/test/core/column/column_const_test.cpp
@@ -41,6 +41,15 @@ TEST(ColumnConstTest, TestCreate) {
     EXPECT_TRUE(!is_column_const(column_const2->get_data_column()));
 }
 
+TEST(ColumnConstTest, IsExclusiveChecksNestedColumn) {
+    auto column_data = ColumnHelper::create_column<DataTypeInt64>({7});
+    auto column_const = ColumnConst::create(column_data, 3);
+
+    EXPECT_FALSE(column_const->is_exclusive());
+    column_data.reset();
+    EXPECT_TRUE(column_const->is_exclusive());
+}
+
 TEST(ColumnConstTest, clone_resized_clones_nested_data) {
     auto column_data = ColumnHelper::create_column<DataTypeInt64>({7});
     auto column_const = ColumnConst::create(column_data, 3);
@@ -322,4 +331,4 @@ TEST(ColumnConstTest, replace_float_special_values) {
         column_const->finalize();
     }
 }
-} // namespace doris
\ No newline at end of file
+} // namespace doris
diff --git a/be/test/core/column/column_nullable_test.cpp 
b/be/test/core/column/column_nullable_test.cpp
index 77f167c9ea8..088e2071795 100644
--- a/be/test/core/column/column_nullable_test.cpp
+++ b/be/test/core/column/column_nullable_test.cpp
@@ -136,6 +136,13 @@ TEST(ColumnNullableTest, 
SharedCreatePreservesImmutableSubcolumns) {
     EXPECT_EQ(nullable_ref.get_null_map_column_ptr().get(), 
null_map_alias.get());
     EXPECT_EQ(nested_alias->size(), 1);
     EXPECT_EQ(null_map_alias->size(), 1);
+    EXPECT_FALSE(nullable->is_exclusive());
+
+    nested.reset();
+    nested_alias.reset();
+    null_map.reset();
+    null_map_alias.reset();
+    EXPECT_TRUE(nullable->is_exclusive());
 }
 
 TEST(ColumnNullableTest, UpdateCrc32cBatchKeepsBlockInsertable) {
diff --git a/be/test/core/column/column_variant_test.cpp 
b/be/test/core/column/column_variant_test.cpp
index b7c901c8c71..6f5568730f3 100644
--- a/be/test/core/column/column_variant_test.cpp
+++ b/be/test/core/column/column_variant_test.cpp
@@ -1832,16 +1832,36 @@ TEST_F(ColumnVariantTest, is_scalar_variant) {
 }
 
 TEST_F(ColumnVariantTest, is_exclusive) {
-    auto test_func = [](const auto& source_column) {
-        auto src_size = source_column->size();
-        EXPECT_TRUE(src_size > 0);
+    auto variant = VariantUtil::construct_basic_varint_column();
+    EXPECT_GT(variant->size(), 0);
+    EXPECT_TRUE(variant->is_exclusive());
 
-        // Test is_exclusive
-        bool is_exclusive = source_column->is_exclusive();
-        // The result depends on the actual data structure
-        EXPECT_TRUE(is_exclusive);
-    };
-    test_func(column_variant);
+    const auto& subcolumns = variant->get_subcolumns();
+    const auto* root = subcolumns.get_root();
+    ColumnPtr shared_subcolumn;
+    for (const auto& entry : subcolumns) {
+        if (entry.get() != root && !entry->data.data.empty()) {
+            shared_subcolumn = static_cast<const 
IColumn::Ptr&>(entry->data.data[0]);
+            break;
+        }
+    }
+    ASSERT_TRUE(shared_subcolumn);
+    EXPECT_FALSE(variant->is_exclusive());
+
+    shared_subcolumn.reset();
+    EXPECT_TRUE(variant->is_exclusive());
+
+    auto shared_sparse_column = variant->get_sparse_column();
+    EXPECT_FALSE(variant->is_exclusive());
+
+    shared_sparse_column.reset();
+    EXPECT_TRUE(variant->is_exclusive());
+
+    auto shared_doc_value_column = variant->get_doc_value_column();
+    EXPECT_FALSE(variant->is_exclusive());
+
+    shared_doc_value_column.reset();
+    EXPECT_TRUE(variant->is_exclusive());
 }
 
 TEST_F(ColumnVariantTest, get_root_type) {
diff --git a/be/test/exec/operator/operator_projection_test.cpp 
b/be/test/exec/operator/operator_projection_test.cpp
new file mode 100644
index 00000000000..02ddc5f7f9e
--- /dev/null
+++ b/be/test/exec/operator/operator_projection_test.cpp
@@ -0,0 +1,81 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "common/object_pool.h"
+#include "core/data_type/data_type_number.h"
+#include "exec/operator/mock_operator.h"
+#include "runtime/runtime_profile.h"
+#include "testutil/column_helper.h"
+#include "testutil/mock/mock_descriptors.h"
+#include "testutil/mock/mock_runtime_state.h"
+#include "testutil/mock/mock_slot_ref.h"
+
+namespace doris {
+
+TEST(OperatorProjectionTest, PublishesSharedColumnAndReusesOutputBlock) {
+    ObjectPool pool;
+    auto data_type = std::make_shared<DataTypeInt32>();
+    auto row_descriptor = MockRowDescriptor({data_type}, &pool);
+
+    MockOperatorX op;
+    op._row_descriptor = row_descriptor;
+    op._output_row_descriptor =
+            std::make_unique<MockRowDescriptor>(std::vector<DataTypePtr> 
{data_type}, &pool);
+
+    MockRuntimeState state;
+    const auto max_operator_id = op.operator_id() - 1;
+    state.resize_op_id_to_local_state(max_operator_id);
+    state.set_max_operator_id(max_operator_id);
+    RuntimeProfile parent_profile("parent");
+    LocalStateInfo info {&parent_profile, {}, nullptr, {}, 0};
+    ASSERT_TRUE(op.setup_local_state(&state, info).ok());
+
+    auto* local_state = state.get_local_state(op.operator_id());
+    local_state->_projections = MockSlotRef::create_mock_contexts(0, 
data_type);
+
+    std::vector<int32_t> first_values(1 << 18, 7);
+    Block first_origin = 
ColumnHelper::create_block<DataTypeInt32>(first_values);
+    const auto* first_column = first_origin.get_by_position(0).column.get();
+    const auto first_allocated_bytes = 
static_cast<int64_t>(first_origin.allocated_bytes());
+
+    Block output;
+    ASSERT_TRUE(op.do_projections(&state, &first_origin, &output).ok());
+    EXPECT_EQ(output.get_by_position(0).column.get(), first_column);
+    EXPECT_EQ(output.rows(), first_values.size());
+    EXPECT_EQ(output.get_by_position(0).column->get_int(0), 7);
+    EXPECT_EQ(first_origin.rows(), 0);
+    EXPECT_LT(local_state->estimate_memory_usage(), first_allocated_bytes);
+
+    output.clear_column_data();
+    Block second_origin = ColumnHelper::create_block<DataTypeInt32>({8, 9});
+    const auto* second_column = second_origin.get_by_position(0).column.get();
+
+    ASSERT_TRUE(op.do_projections(&state, &second_origin, &output).ok());
+    EXPECT_EQ(output.get_by_position(0).column.get(), second_column);
+    EXPECT_EQ(output.rows(), 2);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(0), 8);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(1), 9);
+    EXPECT_EQ(second_origin.rows(), 0);
+}
+
+} // namespace doris
diff --git a/be/test/exec/scan/scanner_late_arrival_rf_test.cpp 
b/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
index 51729f2eb35..2b7a7154f24 100644
--- a/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
+++ b/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
@@ -213,4 +213,49 @@ TEST(ScannerProjectionTest, 
projects_incompatible_blocks_before_reading_the_next
     EXPECT_EQ(final_output.rows(), 0);
 }
 
+TEST(ScannerProjectionTest, publishes_shared_column_and_reuses_output_block) {
+    ObjectPool pool;
+    auto data_type = std::make_shared<DataTypeInt32>();
+    auto row_descriptor = MockRowDescriptor({data_type}, &pool);
+
+    MockRuntimeState state;
+    state._batch_size = 4;
+
+    auto op = std::make_shared<MockScanOperatorX>();
+    op->_row_descriptor = row_descriptor;
+    op->_output_row_descriptor =
+            std::make_unique<MockRowDescriptor>(std::vector<DataTypePtr> 
{data_type}, &pool);
+    op->_output_tuple_desc = 
op->_output_row_descriptor->tuple_descriptors()[0];
+
+    auto local_state = std::make_shared<MockScanLocalState>(&state, op.get());
+    local_state->_projections = MockSlotRef::create_mock_contexts(0, 
data_type);
+
+    RuntimeProfile profile("scanner");
+    TestScanner scanner(&state, local_state.get(), -1, &profile);
+    ASSERT_TRUE(scanner.init(&state, {}).ok());
+
+    Block first_input = ColumnHelper::create_block<DataTypeInt32>({1, 2});
+    const auto* first_column = first_input.get_by_position(0).column.get();
+    scanner.add_block(std::move(first_input));
+
+    Block second_input = ColumnHelper::create_block<DataTypeInt32>({3, 4});
+    const auto* second_column = second_input.get_by_position(0).column.get();
+    scanner.add_block(std::move(second_input));
+
+    Block output;
+    bool eos = false;
+    ASSERT_TRUE(scanner.get_block_after_projects(&state, &output, &eos).ok());
+    EXPECT_FALSE(eos);
+    EXPECT_EQ(output.get_by_position(0).column.get(), first_column);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(0), 1);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(1), 2);
+
+    output.clear_column_data();
+    ASSERT_TRUE(scanner.get_block_after_projects(&state, &output, &eos).ok());
+    EXPECT_FALSE(eos);
+    EXPECT_EQ(output.get_by_position(0).column.get(), second_column);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(0), 3);
+    EXPECT_EQ(output.get_by_position(0).column->get_int(1), 4);
+}
+
 } // 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