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 c611239b7a3  [opt](exec) Avoid copies when publishing projection 
results (#66085)
c611239b7a3 is described below

commit c611239b7a3ee0c42c61136248a8fda44a838f43
Author: Mryange <[email protected]>
AuthorDate: Fri Jul 31 16:14:46 2026 +0800

     [opt](exec) Avoid copies when publishing projection results (#66085)
    
    ### What problem does this PR solve?
    
    
    Projection results were converted to mutable columns before being
    published. Shared results such as SlotRef columns therefore triggered
    COW clones and full-column copies.
    
    This change keeps scoped output-block reuse while moving exclusive
    results directly and publishing shared immutable columns after restore.
    It also makes `ColumnConst` and `ColumnVariant` ownership checks include
    their nested columns, and uses actual peak memory tracking instead of
    charging shared input buffers as projection allocations.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 be/src/core/column/column_const.h                  |   2 +
 be/src/core/column/column_variant.cpp              |  14 +++
 be/src/core/column/column_variant.h                |   2 +
 be/src/exec/operator/operator.cpp                  | 109 ++++++++++-----------
 be/src/exec/operator/operator.h                    |   4 -
 be/src/exec/scan/scanner.cpp                       |  65 ++++++------
 be/test/core/column/column_const_test.cpp          |   9 ++
 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 +++++++++
 11 files changed, 279 insertions(+), 97 deletions(-)

diff --git a/be/src/core/column/column_const.h 
b/be/src/core/column/column_const.h
index 834bfc2a08c..f4a373f3178 100644
--- a/be/src/core/column/column_const.h
+++ b/be/src/core/column/column_const.h
@@ -119,6 +119,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; }
diff --git a/be/src/core/column/column_variant.cpp 
b/be/src/core/column/column_variant.cpp
index c1ae100bf47..88d72ee5d91 100644
--- a/be/src/core/column/column_variant.cpp
+++ b/be/src/core/column/column_variant.cpp
@@ -829,6 +829,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::mutate_subcolumns() {
     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 a650555f49e..d4e7e005494 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 mutate_subcolumns() override;
     void for_each_subcolumn(ColumnCallback callback) const override;
 
diff --git a/be/src/exec/operator/operator.cpp 
b/be/src/exec/operator/operator.cpp
index 1f255078b5d..cfc9d664511 100644
--- a/be/src/exec/operator/operator.cpp
+++ b/be/src/exec/operator/operator.cpp
@@ -323,63 +323,45 @@ 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) {
-        if (projections.empty()) {
-            return Status::InternalError("meet empty intermediate projection, 
node id: {}",
-                                         node_id());
-        }
-        new_columns.resize(projections.size());
-        for (int i = 0; i < projections.size(); i++) {
-            RETURN_IF_ERROR(projections[i]->execute(&input_block, 
new_columns[i]));
-            if (new_columns[i].column->size() != rows) {
-                return Status::InternalError(
-                        "intermediate projection result column size {} not 
equal input rows {}, "
-                        "expr: {}",
-                        new_columns[i].column->size(), rows,
-                        projections[i]->root()->debug_string());
-            }
-        }
-        Block tmp_block {new_columns};
-        bytes_usage += tmp_block.allocated_bytes();
-        input_block.swap(tmp_block);
-    }
-
-    if (input_block.rows() != rows) {
-        return Status::InternalError(
-                "after intermediate projections input block rows {} not equal 
origin rows {}, "
-                "input_block: {}",
-                input_block.rows(), rows, input_block.dump_structure());
-    }
-    auto insert_column_datas = [&](auto& to, ColumnPtr& from, size_t rows) {
-        if (is_column_nullable(*to) && !is_column_nullable(*from)) {
-            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 = make_nullable(from, false)->assert_mutable();
+    SCOPED_PEAK_MEM(&local_state->_estimate_memory_usage);
+
+    {
+        Block input_block = *origin_block;
+
+        ColumnsWithTypeAndName new_columns;
+        for (const auto& projections : local_state->_intermediate_projections) 
{
+            if (projections.empty()) {
+                return Status::InternalError("meet empty intermediate 
projection, node id: {}",
+                                             node_id());
             }
-        } else {
-            if (_keep_origin || !from->is_exclusive()) {
-                to->insert_range_from(*from, 0, rows);
-                bytes_usage += from->allocated_bytes();
-            } else {
-                to = from->assert_mutable();
+            new_columns.resize(projections.size());
+            for (int i = 0; i < projections.size(); i++) {
+                RETURN_IF_ERROR(projections[i]->execute(&input_block, 
new_columns[i]));
+                if (new_columns[i].column->size() != rows) {
+                    return Status::InternalError(
+                            "intermediate projection result column size {} not 
equal input rows "
+                            "{}, expr: {}",
+                            new_columns[i].column->size(), rows,
+                            projections[i]->root()->debug_string());
+                }
             }
+            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) {
+        if (input_block.rows() != rows) {
+            return Status::InternalError(
+                    "after intermediate projections input block rows {} not 
equal origin rows {}, "
+                    "input_block: {}",
+                    input_block.rows(), rows, input_block.dump_structure());
+        }
+
+        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));
@@ -390,12 +372,27 @@ Status OperatorXBase::do_projections(RuntimeState* state, 
Block* origin_block,
                         local_state->_projections[i]->root()->debug_string());
             }
             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 4654ee7e542..becffbb171e 100644
--- a/be/src/exec/operator/operator.h
+++ b/be/src/exec/operator/operator.h
@@ -1014,10 +1014,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 3eaf5ee77eb..cbdf5fa7eee 100644
--- a/be/src/exec/scan/scanner.cpp
+++ b/be/src/exec/scan/scanner.cpp
@@ -210,41 +210,50 @@ 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 columns was moved into output_block, so we need to set 
origin_block to empty columns
-    auto empty_columns = origin_block->clone_empty_columns();
-    origin_block->set_columns(std::move(empty_columns));
+    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/test/core/column/column_const_test.cpp 
b/be/test/core/column/column_const_test.cpp
index da0c8e103ec..3056d5276a8 100644
--- a/be/test/core/column/column_const_test.cpp
+++ b/be/test/core/column/column_const_test.cpp
@@ -42,6 +42,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, ConstNullableIsSemanticallyNullable) {
     auto nullable_data = 
ColumnHelper::create_nullable_column<DataTypeInt64>({7}, {0});
     auto const_nullable = ColumnConst::create(nullable_data, 3);
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 9494212fef5..9275979cab0 100644
--- a/be/test/core/column/column_variant_test.cpp
+++ b/be/test/core/column/column_variant_test.cpp
@@ -1817,16 +1817,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 0d31b694951..960eba0f9c1 100644
--- a/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
+++ b/be/test/exec/scan/scanner_late_arrival_rf_test.cpp
@@ -165,4 +165,49 @@ TEST(ScannerProjectionTest, 
merges_padding_block_when_limit_eos_without_extra_fl
     EXPECT_EQ(first_output.rows(), 7);
 }
 
+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


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

Reply via email to