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]