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 c5a8e0d1c38 [refine](column) separate mutable subcolumn mutation from 
read-only traversal (#65087)
c5a8e0d1c38 is described below

commit c5a8e0d1c3823f6d8e402e2a8cd669f4cf2d7efb
Author: Mryange <[email protected]>
AuthorDate: Fri Jul 3 14:25:51 2026 +0800

    [refine](column) separate mutable subcolumn mutation from read-only 
traversal (#65087)
    
    ### What problem does this PR solve?
    
    
    Column wrappers used `for_each_subcolumn` for both read-only traversal
    and mutable subcolumn detachment during COW mutation. These two
    operations have different contracts: read-only paths only need `const
    IColumn&`, while COW mutation needs to replace child pointer slots.
    
    Root cause: the old mutable callback accepted `IColumn::WrappedPtr&`,
    but several composite columns store strongly typed child wrappers, such
    as nullable null maps and array/map offsets. That forced ad-hoc move-out
    and cast-back bridges, and made read-only callers use mutable traversal
    or `const_cast`.
    
    This change keeps `for_each_subcolumn` as a const read-only traversal
    API and adds `mutate_subcolumns()` for the COW mutation path. Composite
    columns now detach children through `mutate_subcolumn` helpers, and
    read-only recursive checks use the const traversal directly. A dedicated
    BE unit test file verifies that mutating exclusive subcolumns does not
    introduce extra clones, while shared subcolumns are still detached.
    
    
    ### Release note
    
    None
---
 be/src/core/block/block.cpp                        |  10 +-
 be/src/core/column/column.cpp                      |  37 ++-
 be/src/core/column/column.h                        |  33 ++-
 be/src/core/column/column_array.h                  |  17 +-
 be/src/core/column/column_const.h                  |   6 +-
 be/src/core/column/column_map.h                    |  20 +-
 be/src/core/column/column_nullable.h               |  15 +-
 be/src/core/column/column_struct.cpp               |  10 +-
 be/src/core/column/column_struct.h                 |   3 +-
 be/src/core/column/column_variant.cpp              |  31 ++-
 be/src/core/column/column_variant.h                |   3 +-
 .../core/column/column_mutate_subcolumns_test.cpp  | 304 +++++++++++++++++++++
 12 files changed, 410 insertions(+), 79 deletions(-)

diff --git a/be/src/core/block/block.cpp b/be/src/core/block/block.cpp
index 4dd6530f55d..88006f7b2fd 100644
--- a/be/src/core/block/block.cpp
+++ b/be/src/core/block/block.cpp
@@ -89,17 +89,13 @@ bool is_recursively_exclusive(const IColumn& column) {
     }
 
     bool exclusive = true;
-    IColumn::ColumnCallback callback = [&](IColumn::WrappedPtr& subcolumn) {
+    IColumn::ColumnCallback callback = [&](const IColumn& subcolumn) {
         if (!exclusive) {
             return;
         }
-        const ColumnPtr& subcolumn_ptr = const_cast<const 
IColumn::WrappedPtr&>(subcolumn);
-        DCHECK(subcolumn_ptr);
-        exclusive = is_recursively_exclusive(*subcolumn_ptr);
+        exclusive = is_recursively_exclusive(subcolumn);
     };
-    // `for_each_subcolumn` only exposes a mutable callback type. This callback
-    // only reads the wrapped pointers and never calls the non-const accessors.
-    const_cast<IColumn&>(column).for_each_subcolumn(callback);
+    column.for_each_subcolumn(callback);
     return exclusive;
 }
 
diff --git a/be/src/core/column/column.cpp b/be/src/core/column/column.cpp
index 3fea47f9388..68048b10053 100644
--- a/be/src/core/column/column.cpp
+++ b/be/src/core/column/column.cpp
@@ -32,12 +32,11 @@ std::string IColumn::dump_structure() const {
     std::stringstream res;
     res << get_name() << "(size = " << size();
 
-    ColumnCallback callback = [&](ColumnPtr& subcolumn) {
-        res << ", " << subcolumn->dump_structure();
+    ColumnCallback callback = [&](const IColumn& subcolumn) {
+        res << ", " << subcolumn.dump_structure();
     };
 
-    // simply read using for_each_subcolumn without modification; const_cast 
can be used.
-    const_cast<IColumn*>(this)->for_each_subcolumn(callback);
+    for_each_subcolumn(callback);
 
     res << ")";
     return res.str();
@@ -45,11 +44,10 @@ std::string IColumn::dump_structure() const {
 
 int IColumn::count_const_column() const {
     int count = is_column_const(*this) ? 1 : 0;
-    ColumnCallback callback = [&](ColumnPtr& subcolumn) {
-        count += subcolumn->count_const_column();
+    ColumnCallback callback = [&](const IColumn& subcolumn) {
+        count += subcolumn.count_const_column();
     };
-    // simply read using for_each_subcolumn without modification; const_cast 
can be used.
-    const_cast<IColumn*>(this)->for_each_subcolumn(callback);
+    for_each_subcolumn(callback);
     return count;
 }
 
@@ -95,13 +93,12 @@ bool IColumn::column_boolean_check() const {
     };
 
     bool is_valid = check_boolean_is_zero_or_one(*this);
-    ColumnCallback callback = [&](ColumnPtr& subcolumn) {
-        if (!subcolumn->column_boolean_check()) {
+    ColumnCallback callback = [&](const IColumn& subcolumn) {
+        if (!subcolumn.column_boolean_check()) {
             is_valid = false;
         }
     };
-    // simply read using for_each_subcolumn without modification; const_cast 
can be used.
-    const_cast<IColumn*>(this)->for_each_subcolumn(callback);
+    for_each_subcolumn(callback);
     return is_valid;
 }
 
@@ -122,13 +119,12 @@ bool IColumn::null_map_check() const {
     };
 
     bool is_valid = check_null_map_is_zero_or_one(*this);
-    ColumnCallback callback = [&](ColumnPtr& subcolumn) {
-        if (!subcolumn->null_map_check()) {
+    ColumnCallback callback = [&](const IColumn& subcolumn) {
+        if (!subcolumn.null_map_check()) {
             is_valid = false;
         }
     };
-    // simply read using for_each_subcolumn without modification; const_cast 
can be used.
-    const_cast<IColumn*>(this)->for_each_subcolumn(callback);
+    for_each_subcolumn(callback);
     return is_valid;
 }
 
@@ -231,15 +227,14 @@ bool is_column_const(const IColumn& column) {
 }
 
 void IColumn::check_const_only_in_top_level() const {
-    ColumnCallback throw_if_const = [&](WrappedPtr& column) {
-        const ColumnPtr& col = const_cast<const WrappedPtr&>(column);
-        if (is_column_const(*col)) {
+    ColumnCallback throw_if_const = [&](const IColumn& column) {
+        if (is_column_const(column)) {
             throw doris::Exception(ErrorCode::INTERNAL_ERROR,
                                    "const column is not allowed to be nested, 
but got {}",
-                                   col->get_name());
+                                   column.get_name());
         }
     };
-    const_cast<IColumn*>(this)->for_each_subcolumn(throw_if_const);
+    for_each_subcolumn(throw_if_const);
 }
 
 } // namespace doris
diff --git a/be/src/core/column/column.h b/be/src/core/column/column.h
index 730bb206e69..f27cbd1646e 100644
--- a/be/src/core/column/column.h
+++ b/be/src/core/column/column.h
@@ -574,10 +574,27 @@ public:
 
     /// If the column contains subcolumns (such as Array, Nullable, etc), do 
callback on them.
     /// Shallow: doesn't do recursive calls; don't do call for itself.
-    using ColumnCallback = std::function<void(WrappedPtr&)>;
-    using ImutableColumnCallback = std::function<void(const IColumn&)>;
-    virtual void for_each_subcolumn(ColumnCallback) {}
+    using ColumnCallback = std::function<void(const IColumn&)>;
+    virtual void for_each_subcolumn(ColumnCallback) const {}
 
+protected:
+    virtual void mutate_subcolumns() {}
+
+    static void mutate_subcolumn(WrappedPtr& subcolumn) {
+        static_cast<IColumn::Ptr&>(subcolumn) =
+                std::move(*static_cast<const 
IColumn::Ptr&>(subcolumn)).mutate();
+    }
+
+    template <typename ColumnType>
+    static void mutate_subcolumn(typename ColumnType::WrappedPtr& subcolumn) {
+        auto mutated = std::move(*static_cast<const typename 
ColumnType::Ptr&>(subcolumn)).mutate();
+        auto typed_mutated = ColumnType::cast_to_column_mutptr(
+                assert_cast<ColumnType*, 
TypeCheckOnRelease::DISABLE>(mutated.get()));
+        mutated = nullptr;
+        static_cast<typename ColumnType::Ptr&>(subcolumn) = 
std::move(typed_mutated);
+    }
+
+public:
     /// Columns have equal structure.
     /// If true - you can use "compare_at", "insert_from", etc. methods.
     virtual bool structure_equals(const IColumn&) const {
@@ -591,10 +608,7 @@ public:
     // exclusive nodes are reused through the COW fast path.
     MutablePtr mutate() const&& {
         MutablePtr res = shallow_mutate();
-        res->for_each_subcolumn([](WrappedPtr& subcolumn) {
-            static_cast<IColumn::Ptr&>(subcolumn) =
-                    std::move(*static_cast<const 
IColumn::Ptr&>(subcolumn)).mutate();
-        });
+        res->mutate_subcolumns();
         return res;
     }
 
@@ -605,10 +619,7 @@ public:
     static MutablePtr mutate(Ptr ptr) {
         MutablePtr res = ptr->shallow_mutate(); /// Now use_count is 2.
         ptr.reset();                            /// Reset use_count to 1.
-        res->for_each_subcolumn([](WrappedPtr& subcolumn) {
-            static_cast<IColumn::Ptr&>(subcolumn) =
-                    std::move(*static_cast<const 
IColumn::Ptr&>(subcolumn)).mutate();
-        });
+        res->mutate_subcolumns();
         return res;
     }
 
diff --git a/be/src/core/column/column_array.h 
b/be/src/core/column/column_array.h
index 9c4e37ed794..645b1662d7b 100644
--- a/be/src/core/column/column_array.h
+++ b/be/src/core/column/column_array.h
@@ -37,7 +37,6 @@
 #include "core/field.h"
 #include "core/string_ref.h"
 #include "core/types.h"
-#include "util/defer_op.h"
 
 class SipHash;
 
@@ -216,14 +215,14 @@ public:
         return get_offsets()[i] - get_offsets()[i - 1];
     }
 
-    void for_each_subcolumn(ColumnCallback callback) override {
-        IColumn::WrappedPtr 
offsets_column(std::move(static_cast<ColumnOffsets::Ptr&>(offsets)));
-        Defer defer([&] {
-            static_cast<ColumnOffsets::Ptr&>(offsets) =
-                    cast_to_column<ColumnOffsets>(static_cast<const 
IColumn::Ptr&>(offsets_column));
-        });
-        callback(offsets_column);
-        callback(data);
+    void mutate_subcolumns() override {
+        mutate_subcolumn<ColumnOffsets>(offsets);
+        mutate_subcolumn(data);
+    }
+
+    void for_each_subcolumn(ColumnCallback callback) const override {
+        callback(*static_cast<const ColumnOffsets::Ptr&>(offsets));
+        callback(*static_cast<const IColumn::Ptr&>(data));
     }
 
     ColumnPtr convert_column_if_overflow() override {
diff --git a/be/src/core/column/column_const.h 
b/be/src/core/column/column_const.h
index 238d0f971b5..834bfc2a08c 100644
--- a/be/src/core/column/column_const.h
+++ b/be/src/core/column/column_const.h
@@ -261,7 +261,11 @@ public:
         }
     }
 
-    void for_each_subcolumn(ColumnCallback callback) override { 
callback(data); }
+    void mutate_subcolumns() override { mutate_subcolumn(data); }
+
+    void for_each_subcolumn(ColumnCallback callback) const override {
+        callback(*static_cast<const IColumn::Ptr&>(data));
+    }
 
     bool structure_equals(const IColumn& rhs) const override {
         if (const auto* rhs_concrete = 
check_and_get_column<ColumnConst>(&rhs)) {
diff --git a/be/src/core/column/column_map.h b/be/src/core/column/column_map.h
index f5bb29d8be5..0781a2cb503 100644
--- a/be/src/core/column/column_map.h
+++ b/be/src/core/column/column_map.h
@@ -43,7 +43,6 @@
 #include "core/string_ref.h"
 #include "core/types.h"
 #include "exec/common/sip_hash.h"
-#include "util/defer_op.h"
 
 class SipHash;
 
@@ -74,15 +73,16 @@ public:
 
     std::string get_name() const override;
 
-    void for_each_subcolumn(ColumnCallback callback) override {
-        IColumn::WrappedPtr 
offsets(std::move(static_cast<COffsets::Ptr&>(offsets_column)));
-        Defer defer([&] {
-            static_cast<COffsets::Ptr&>(offsets_column) =
-                    cast_to_column<COffsets>(static_cast<const 
IColumn::Ptr&>(offsets));
-        });
-        callback(keys_column);
-        callback(values_column);
-        callback(offsets);
+    void mutate_subcolumns() override {
+        mutate_subcolumn(keys_column);
+        mutate_subcolumn(values_column);
+        mutate_subcolumn<COffsets>(offsets_column);
+    }
+
+    void for_each_subcolumn(ColumnCallback callback) const override {
+        callback(*static_cast<const IColumn::Ptr&>(keys_column));
+        callback(*static_cast<const IColumn::Ptr&>(values_column));
+        callback(*static_cast<const COffsets::Ptr&>(offsets_column));
     }
 
     void sanity_check() const override {
diff --git a/be/src/core/column/column_nullable.h 
b/be/src/core/column/column_nullable.h
index 14a764c4aca..de0e6b64e5c 100644
--- a/be/src/core/column/column_nullable.h
+++ b/be/src/core/column/column_nullable.h
@@ -32,7 +32,6 @@
 #include "core/typeid_cast.h"
 #include "core/types.h"
 #include "storage/olap_common.h"
-#include "util/defer_op.h"
 
 class SipHash;
 
@@ -251,14 +250,14 @@ public:
         return get_ptr();
     }
 
-    void for_each_subcolumn(ColumnCallback callback) override {
-        callback(_nested_column);
+    void mutate_subcolumns() override {
+        mutate_subcolumn(_nested_column);
+        mutate_subcolumn<ColumnUInt8>(_null_map);
+    }
 
-        IColumn::WrappedPtr 
null_map(std::move(static_cast<ColumnUInt8::Ptr&>(_null_map)));
-        Defer defer([&] {
-            _null_map = cast_to_column<ColumnUInt8>(static_cast<const 
IColumn::Ptr&>(null_map));
-        });
-        callback(null_map);
+    void for_each_subcolumn(ColumnCallback callback) const override {
+        callback(*static_cast<const IColumn::Ptr&>(_nested_column));
+        callback(*static_cast<const ColumnUInt8::Ptr&>(_null_map));
     }
 
     bool structure_equals(const IColumn& rhs) const override {
diff --git a/be/src/core/column/column_struct.cpp 
b/be/src/core/column/column_struct.cpp
index e2a90432c56..fb15785df8b 100644
--- a/be/src/core/column/column_struct.cpp
+++ b/be/src/core/column/column_struct.cpp
@@ -379,9 +379,15 @@ bool ColumnStruct::has_enough_capacity(const IColumn& src) 
const {
     return true;
 }
 
-void ColumnStruct::for_each_subcolumn(ColumnCallback callback) {
+void ColumnStruct::mutate_subcolumns() {
     for (auto& column : columns) {
-        callback(column);
+        mutate_subcolumn(column);
+    }
+}
+
+void ColumnStruct::for_each_subcolumn(ColumnCallback callback) const {
+    for (const auto& column : columns) {
+        callback(*static_cast<const IColumn::Ptr&>(column));
     }
 }
 
diff --git a/be/src/core/column/column_struct.h 
b/be/src/core/column/column_struct.h
index e1f81950ddc..83affe72965 100644
--- a/be/src/core/column/column_struct.h
+++ b/be/src/core/column/column_struct.h
@@ -155,7 +155,8 @@ public:
     size_t byte_size() const override;
     size_t allocated_bytes() const override;
     bool has_enough_capacity(const IColumn& src) const override;
-    void for_each_subcolumn(ColumnCallback callback) override;
+    void mutate_subcolumns() override;
+    void for_each_subcolumn(ColumnCallback callback) const override;
     bool structure_equals(const IColumn& rhs) const override;
 
     size_t tuple_size() const { return columns.size(); }
diff --git a/be/src/core/column/column_variant.cpp 
b/be/src/core/column/column_variant.cpp
index 723d52d46b9..ec34c619cdc 100644
--- a/be/src/core/column/column_variant.cpp
+++ b/be/src/core/column/column_variant.cpp
@@ -68,7 +68,6 @@
 #include "exprs/aggregate/aggregate_function.h"
 #include "exprs/json_functions.h"
 #include "storage/olap_common.h"
-#include "util/defer_op.h"
 #include "util/json/path_in_data.h"
 #include "util/jsonb_document.h"
 #include "util/jsonb_document_cast.h"
@@ -826,19 +825,28 @@ size_t ColumnVariant::allocated_bytes() const {
     return res;
 }
 
-void ColumnVariant::for_each_subcolumn(ColumnCallback callback) {
+void ColumnVariant::mutate_subcolumns() {
     for (auto& entry : subcolumns) {
         for (auto& part : entry->data.data) {
-            callback(part);
+            mutate_subcolumn(part);
         }
     }
-    callback(serialized_sparse_column);
-    callback(serialized_doc_value_column);
-    // callback may be filter, so the row count may be changed
+    mutate_subcolumn(serialized_sparse_column);
+    mutate_subcolumn(serialized_doc_value_column);
     num_rows = serialized_sparse_column->size();
     ENABLE_CHECK_CONSISTENCY(this);
 }
 
+void ColumnVariant::for_each_subcolumn(ColumnCallback callback) const {
+    for (const auto& entry : subcolumns) {
+        for (const auto& part : entry->data.data) {
+            callback(*static_cast<const IColumn::Ptr&>(part));
+        }
+    }
+    callback(*static_cast<const IColumn::Ptr&>(serialized_sparse_column));
+    callback(*static_cast<const IColumn::Ptr&>(serialized_doc_value_column));
+}
+
 void ColumnVariant::insert_from(const IColumn& src, size_t n) {
     const auto* src_v = assert_cast<const ColumnVariant*>(&src);
     ENABLE_CHECK_CONSISTENCY(src_v);
@@ -2375,7 +2383,7 @@ size_t ColumnVariant::filter(const Filter& filter) {
         for (auto& subcolumn : subcolumns) {
             subcolumn->data.num_rows = count;
         }
-        for_each_subcolumn([&](auto& part) {
+        auto filter_part = [&](IColumn::WrappedPtr& part) {
             if (part->size() != count) {
                 if (part->is_exclusive()) {
                     const auto result_size = part->filter(filter);
@@ -2390,7 +2398,14 @@ size_t ColumnVariant::filter(const Filter& filter) {
                     part = part->filter(filter, count);
                 }
             }
-        });
+        };
+        for (auto& entry : subcolumns) {
+            for (auto& part : entry->data.data) {
+                filter_part(part);
+            }
+        }
+        filter_part(serialized_sparse_column);
+        filter_part(serialized_doc_value_column);
     }
     num_rows = count;
     ENABLE_CHECK_CONSISTENCY(this);
diff --git a/be/src/core/column/column_variant.h 
b/be/src/core/column/column_variant.h
index 1d5c4eed137..a650555f49e 100644
--- a/be/src/core/column/column_variant.h
+++ b/be/src/core/column/column_variant.h
@@ -469,7 +469,8 @@ public:
 
     bool has_enough_capacity(const IColumn& src) const override { return 
false; }
 
-    void for_each_subcolumn(ColumnCallback callback) override;
+    void mutate_subcolumns() override;
+    void for_each_subcolumn(ColumnCallback callback) const override;
 
     // Do nothing, call try_insert instead
     void insert(const Field& field) override { try_insert(field); }
diff --git a/be/test/core/column/column_mutate_subcolumns_test.cpp 
b/be/test/core/column/column_mutate_subcolumns_test.cpp
new file mode 100644
index 00000000000..c46179afa87
--- /dev/null
+++ b/be/test/core/column/column_mutate_subcolumns_test.cpp
@@ -0,0 +1,304 @@
+// 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 <initializer_list>
+#include <memory>
+
+#include "common/logging.h"
+#include "core/column/column_array.h"
+#include "core/column/column_const.h"
+#include "core/column/column_map.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_struct.h"
+#include "core/column/column_variant.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_number.h"
+
+namespace doris {
+namespace {
+
+ColumnInt64::MutablePtr create_int64_column(int64_t value) {
+    auto column = ColumnInt64::create();
+    column->insert_value(value);
+    return column;
+}
+
+ColumnInt64::MutablePtr create_int64_column(std::initializer_list<int64_t> 
values) {
+    auto column = ColumnInt64::create();
+    for (const auto value : values) {
+        column->insert_value(value);
+    }
+    return column;
+}
+
+ColumnUInt8::MutablePtr create_uint8_column(uint8_t value) {
+    auto column = ColumnUInt8::create();
+    column->insert_value(value);
+    return column;
+}
+
+ColumnArray::ColumnOffsets::MutablePtr create_single_element_offsets() {
+    auto offsets = ColumnArray::ColumnOffsets::create();
+    offsets->insert_value(1);
+    return offsets;
+}
+
+ColumnVariant::MutablePtr create_variant_column() {
+    auto variant = ColumnVariant::create(0, false);
+    variant->create_root(std::make_shared<DataTypeInt64>(), 
create_int64_column({1, 2}));
+    CHECK(variant->add_sub_column(PathInData("v.a"), create_int64_column({10, 
20}),
+                                  std::make_shared<DataTypeInt64>()));
+    variant->finalize();
+    return variant;
+}
+
+} // namespace
+
+TEST(ColumnMutateSubcolumnsTest, NullableKeepsExclusiveSubcolumns) {
+    ColumnPtr nullable = ColumnNullable::create(create_int64_column(10), 
create_uint8_column(0));
+    const auto& nullable_ref = assert_cast<const ColumnNullable&>(*nullable);
+    const auto* nested_raw = nullable_ref.get_nested_column_ptr().get();
+    const auto* null_map_raw = nullable_ref.get_null_map_column_ptr().get();
+
+    auto mutated = IColumn::mutate(std::move(nullable));
+    const auto& mutated_nullable = assert_cast<const 
ColumnNullable&>(*mutated);
+
+    EXPECT_EQ(mutated_nullable.get_nested_column_ptr().get(), nested_raw);
+    EXPECT_EQ(mutated_nullable.get_null_map_column_ptr().get(), null_map_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, NullableDetachesSharedSubcolumns) {
+    ColumnPtr nested = create_int64_column(10);
+    ColumnPtr nested_alias = nested;
+    ColumnPtr null_map = create_uint8_column(0);
+    ColumnPtr null_map_alias = null_map;
+
+    ColumnPtr nullable = ColumnNullable::create(nested, null_map);
+    auto mutated = IColumn::mutate(std::move(nullable));
+    auto& mutated_nullable = assert_cast<ColumnNullable&>(*mutated);
+
+    EXPECT_NE(mutated_nullable.get_nested_column_ptr().get(), 
nested_alias.get());
+    EXPECT_NE(mutated_nullable.get_null_map_column_ptr().get(), 
null_map_alias.get());
+    EXPECT_EQ(mutated_nullable.get_null_map_data()[0], 0);
+
+    mutated_nullable.get_null_map_data()[0] = 1;
+    const auto& original_null_map = assert_cast<const 
ColumnUInt8&>(*null_map_alias);
+    EXPECT_EQ(original_null_map.get_data()[0], 0);
+}
+
+TEST(ColumnMutateSubcolumnsTest, ArrayKeepsExclusiveSubcolumns) {
+    ColumnPtr array = ColumnArray::create(create_int64_column(10), 
create_single_element_offsets());
+    const auto& array_ref = assert_cast<const ColumnArray&>(*array);
+    const auto* data_raw = array_ref.get_data_ptr().get();
+    const auto* offsets_raw = array_ref.get_offsets_ptr().get();
+
+    auto mutated = IColumn::mutate(std::move(array));
+    const auto& mutated_array = assert_cast<const ColumnArray&>(*mutated);
+
+    EXPECT_EQ(mutated_array.get_data_ptr().get(), data_raw);
+    EXPECT_EQ(mutated_array.get_offsets_ptr().get(), offsets_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, ArrayDetachesSharedSubcolumns) {
+    ColumnPtr data = create_int64_column(10);
+    ColumnPtr data_alias = data;
+    ColumnPtr offsets = create_single_element_offsets();
+    ColumnPtr offsets_alias = offsets;
+
+    ColumnPtr array = ColumnArray::create(data, offsets);
+    auto mutated = IColumn::mutate(std::move(array));
+    auto& mutated_array = assert_cast<ColumnArray&>(*mutated);
+
+    EXPECT_NE(mutated_array.get_data_ptr().get(), data_alias.get());
+    EXPECT_NE(mutated_array.get_offsets_ptr().get(), offsets_alias.get());
+
+    assert_cast<ColumnInt64&>(mutated_array.get_data()).get_data()[0] = 20;
+    mutated_array.get_offsets()[0] = 2;
+    EXPECT_EQ(assert_cast<const ColumnInt64&>(*data_alias).get_data()[0], 10);
+    EXPECT_EQ(assert_cast<const 
ColumnArray::ColumnOffsets&>(*offsets_alias).get_data()[0], 1);
+}
+
+TEST(ColumnMutateSubcolumnsTest, MapKeepsExclusiveSubcolumns) {
+    ColumnPtr map = ColumnMap::create(create_int64_column(1), 
create_int64_column(10),
+                                      create_single_element_offsets());
+    const auto& map_ref = assert_cast<const ColumnMap&>(*map);
+    const auto* keys_raw = map_ref.get_keys_ptr().get();
+    const auto* values_raw = map_ref.get_values_ptr().get();
+    const auto* offsets_raw = map_ref.get_offsets_ptr().get();
+
+    auto mutated = IColumn::mutate(std::move(map));
+    const auto& mutated_map = assert_cast<const ColumnMap&>(*mutated);
+
+    EXPECT_EQ(mutated_map.get_keys_ptr().get(), keys_raw);
+    EXPECT_EQ(mutated_map.get_values_ptr().get(), values_raw);
+    EXPECT_EQ(mutated_map.get_offsets_ptr().get(), offsets_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, MapDetachesSharedSubcolumns) {
+    ColumnPtr keys = create_int64_column(1);
+    ColumnPtr keys_alias = keys;
+    ColumnPtr values = create_int64_column(10);
+    ColumnPtr values_alias = values;
+    ColumnPtr offsets = create_single_element_offsets();
+    ColumnPtr offsets_alias = offsets;
+
+    ColumnPtr map = ColumnMap::create(keys, values, offsets);
+    auto mutated = IColumn::mutate(std::move(map));
+    auto& mutated_map = assert_cast<ColumnMap&>(*mutated);
+
+    EXPECT_NE(mutated_map.get_keys_ptr().get(), keys_alias.get());
+    EXPECT_NE(mutated_map.get_values_ptr().get(), values_alias.get());
+    EXPECT_NE(mutated_map.get_offsets_ptr().get(), offsets_alias.get());
+
+    assert_cast<ColumnInt64&>(mutated_map.get_keys()).get_data()[0] = 2;
+    assert_cast<ColumnInt64&>(mutated_map.get_values()).get_data()[0] = 20;
+    mutated_map.get_offsets()[0] = 2;
+    EXPECT_EQ(assert_cast<const ColumnInt64&>(*keys_alias).get_data()[0], 1);
+    EXPECT_EQ(assert_cast<const ColumnInt64&>(*values_alias).get_data()[0], 
10);
+    EXPECT_EQ(assert_cast<const 
ColumnMap::COffsets&>(*offsets_alias).get_data()[0], 1);
+}
+
+TEST(ColumnMutateSubcolumnsTest, ConstKeepsExclusiveSubcolumn) {
+    ColumnPtr column_const = ColumnConst::create(create_int64_column(10), 3);
+    const auto& const_ref = assert_cast<const ColumnConst&>(*column_const);
+    const auto* data_raw = const_ref.get_data_column_ptr().get();
+
+    auto mutated = IColumn::mutate(std::move(column_const));
+    const auto& mutated_const = assert_cast<const ColumnConst&>(*mutated);
+
+    EXPECT_EQ(mutated_const.get_data_column_ptr().get(), data_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, ConstDetachesSharedSubcolumn) {
+    ColumnPtr data = create_int64_column(10);
+    ColumnPtr data_alias = data;
+
+    ColumnPtr column_const = ColumnConst::create(data, 3);
+    auto mutated = IColumn::mutate(std::move(column_const));
+    const auto& mutated_const = assert_cast<const ColumnConst&>(*mutated);
+
+    EXPECT_NE(mutated_const.get_data_column_ptr().get(), data_alias.get());
+    EXPECT_EQ(mutated_const.get_data_column().get_int(0), 10);
+    EXPECT_EQ(assert_cast<const ColumnInt64&>(*data_alias).get_data()[0], 10);
+}
+
+TEST(ColumnMutateSubcolumnsTest, StructKeepsExclusiveSubcolumns) {
+    MutableColumns columns;
+    columns.push_back(create_int64_column(10));
+    columns.push_back(create_uint8_column(1));
+
+    ColumnPtr column_struct = ColumnStruct::create(std::move(columns));
+    const auto& struct_ref = assert_cast<const ColumnStruct&>(*column_struct);
+    const auto* first_raw = struct_ref.get_column_ptr(0).get();
+    const auto* second_raw = struct_ref.get_column_ptr(1).get();
+
+    auto mutated = IColumn::mutate(std::move(column_struct));
+    const auto& mutated_struct = assert_cast<const ColumnStruct&>(*mutated);
+
+    EXPECT_EQ(mutated_struct.get_column_ptr(0).get(), first_raw);
+    EXPECT_EQ(mutated_struct.get_column_ptr(1).get(), second_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, StructDetachesSharedSubcolumns) {
+    ColumnPtr first = create_int64_column(10);
+    ColumnPtr first_alias = first;
+    ColumnPtr second = create_uint8_column(1);
+    ColumnPtr second_alias = second;
+
+    ColumnPtr column_struct = ColumnStruct::create(Columns {first, second});
+    auto mutated = IColumn::mutate(std::move(column_struct));
+    auto& mutated_struct = assert_cast<ColumnStruct&>(*mutated);
+
+    EXPECT_NE(mutated_struct.get_column_ptr(0).get(), first_alias.get());
+    EXPECT_NE(mutated_struct.get_column_ptr(1).get(), second_alias.get());
+
+    assert_cast<ColumnInt64&>(mutated_struct.get_column(0)).get_data()[0] = 20;
+    assert_cast<ColumnUInt8&>(mutated_struct.get_column(1)).get_data()[0] = 2;
+    EXPECT_EQ(assert_cast<const ColumnInt64&>(*first_alias).get_data()[0], 10);
+    EXPECT_EQ(assert_cast<const ColumnUInt8&>(*second_alias).get_data()[0], 1);
+}
+
+TEST(ColumnMutateSubcolumnsTest, VariantKeepsExclusiveSubcolumns) {
+    auto variant = create_variant_column();
+    const auto* root = variant->get_subcolumns().get_root();
+    ASSERT_NE(root, nullptr);
+    ASSERT_EQ(root->data.data.size(), 1);
+    const auto* root_raw = static_cast<const 
IColumn::Ptr&>(root->data.data[0]).get();
+    auto* materialized = variant->get_subcolumn(PathInData("v.a"));
+    ASSERT_NE(materialized, nullptr);
+    ASSERT_EQ(materialized->data.size(), 1);
+    const auto* materialized_raw = static_cast<const 
IColumn::Ptr&>(materialized->data[0]).get();
+    const auto* sparse_raw = variant->get_sparse_column().get();
+    const auto* doc_value_raw = variant->get_doc_value_column().get();
+
+    ColumnPtr variant_ptr = std::move(variant);
+    auto mutated = IColumn::mutate(std::move(variant_ptr));
+    const auto& mutated_variant = assert_cast<const ColumnVariant&>(*mutated);
+    const auto* mutated_root = mutated_variant.get_subcolumns().get_root();
+    ASSERT_NE(mutated_root, nullptr);
+    ASSERT_EQ(mutated_root->data.data.size(), 1);
+    const auto* mutated_materialized = 
mutated_variant.get_subcolumn(PathInData("v.a"));
+    ASSERT_NE(mutated_materialized, nullptr);
+    ASSERT_EQ(mutated_materialized->data.size(), 1);
+
+    EXPECT_EQ(static_cast<const 
IColumn::Ptr&>(mutated_root->data.data[0]).get(), root_raw);
+    EXPECT_EQ(static_cast<const 
IColumn::Ptr&>(mutated_materialized->data[0]).get(),
+              materialized_raw);
+    EXPECT_EQ(mutated_variant.get_sparse_column().get(), sparse_raw);
+    EXPECT_EQ(mutated_variant.get_doc_value_column().get(), doc_value_raw);
+}
+
+TEST(ColumnMutateSubcolumnsTest, VariantDetachesSharedSubcolumns) {
+    auto variant = create_variant_column();
+    const auto* root = variant->get_subcolumns().get_root();
+    ASSERT_NE(root, nullptr);
+    ASSERT_EQ(root->data.data.size(), 1);
+    ColumnPtr root_alias = static_cast<const 
IColumn::Ptr&>(root->data.data[0]);
+    auto* materialized = variant->get_subcolumn(PathInData("v.a"));
+    ASSERT_NE(materialized, nullptr);
+    ASSERT_EQ(materialized->data.size(), 1);
+    ColumnPtr materialized_alias = static_cast<const 
IColumn::Ptr&>(materialized->data[0]);
+    ColumnPtr sparse_alias = variant->get_sparse_column();
+    ColumnPtr doc_value_alias = variant->get_doc_value_column();
+
+    ColumnPtr variant_ptr = std::move(variant);
+    auto mutated = IColumn::mutate(std::move(variant_ptr));
+    auto& mutated_variant = assert_cast<ColumnVariant&>(*mutated);
+    const auto* mutated_root = mutated_variant.get_subcolumns().get_root();
+    ASSERT_NE(mutated_root, nullptr);
+    ASSERT_EQ(mutated_root->data.data.size(), 1);
+    auto* mutated_materialized = 
mutated_variant.get_subcolumn(PathInData("v.a"));
+    ASSERT_NE(mutated_materialized, nullptr);
+    ASSERT_EQ(mutated_materialized->data.size(), 1);
+
+    EXPECT_NE(static_cast<const 
IColumn::Ptr&>(mutated_root->data.data[0]).get(), root_alias.get());
+    EXPECT_NE(static_cast<const 
IColumn::Ptr&>(mutated_materialized->data[0]).get(),
+              materialized_alias.get());
+    EXPECT_NE(mutated_variant.get_sparse_column().get(), sparse_alias.get());
+    EXPECT_NE(mutated_variant.get_doc_value_column().get(), 
doc_value_alias.get());
+
+    IColumn::Filter filter {1, 0};
+    EXPECT_EQ(mutated_variant.filter(filter), 1);
+    EXPECT_EQ(root_alias->size(), 2);
+    EXPECT_EQ(materialized_alias->size(), 2);
+    EXPECT_EQ(sparse_alias->size(), 2);
+    EXPECT_EQ(doc_value_alias->size(), 2);
+}
+
+} // namespace doris


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

Reply via email to