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

yiguolei 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 296d2a29964 [fix](be) Fix lazy reads for default value columns (#65603)
296d2a29964 is described below

commit 296d2a29964cc6e28d4033ac9f3532cf55492920
Author: Chenyang Sun <[email protected]>
AuthorDate: Wed Jul 15 15:13:41 2026 +0800

    [fix](be) Fix lazy reads for default value columns (#65603)
    
    ### What problem does this PR solve?
    
    
    Fix queries that lazily materialize nested fields added by light schema
    change.
    
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [x] 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/storage/segment/column_reader.cpp           |  12 +++
 be/test/storage/segment/column_reader_test.cpp     | 120 +++++++++++++++++++++
 ...test_light_schema_change_lazy_pruned_struct.out |  13 +++
 ...t_light_schema_change_lazy_pruned_struct.groovy |  95 ++++++++++++++++
 4 files changed, 240 insertions(+)

diff --git a/be/src/storage/segment/column_reader.cpp 
b/be/src/storage/segment/column_reader.cpp
index 25886d7515d..db8f2d6819b 100644
--- a/be/src/storage/segment/column_reader.cpp
+++ b/be/src/storage/segment/column_reader.cpp
@@ -3037,6 +3037,12 @@ Status DefaultValueColumnIterator::init(const 
ColumnIteratorOptions& opts) {
 }
 
 Status DefaultValueColumnIterator::next_batch(size_t* n, MutableColumnPtr& 
dst, bool* has_null) {
+    if (!need_to_read()) {
+        _convert_to_place_holder_column(dst, *n);
+        return Status::OK();
+    }
+
+    _recovery_from_place_holder_column(dst);
     *has_null = _default_value_field.is_null();
     _insert_many_default(dst, *n);
     return Status::OK();
@@ -3044,6 +3050,12 @@ Status DefaultValueColumnIterator::next_batch(size_t* n, 
MutableColumnPtr& dst,
 
 Status DefaultValueColumnIterator::read_by_rowids(const rowid_t* rowids, const 
size_t count,
                                                   MutableColumnPtr& dst) {
+    if (!need_to_read()) {
+        _convert_to_place_holder_column(dst, count);
+        return Status::OK();
+    }
+
+    _recovery_from_place_holder_column(dst);
     _insert_many_default(dst, count);
     return Status::OK();
 }
diff --git a/be/test/storage/segment/column_reader_test.cpp 
b/be/test/storage/segment/column_reader_test.cpp
index 8dfe640808e..3dfe42ac7cd 100644
--- a/be/test/storage/segment/column_reader_test.cpp
+++ b/be/test/storage/segment/column_reader_test.cpp
@@ -629,6 +629,126 @@ TEST_F(ColumnReaderTest, 
PlaceHolderRecoveryAfterColumnReplacement) {
     EXPECT_EQ(2, dst->size());
 }
 
+namespace {
+void check_default_value_lazy_output(bool read_by_rowids) {
+    SCOPED_TRACE(read_by_rowids ? "read_by_rowids" : "next_batch");
+
+    DefaultValueColumnIterator iterator(true, "7", false, 
FieldType::OLAP_FIELD_TYPE_INT, 0, 0,
+                                        sizeof(int32_t));
+    ColumnIteratorOptions iter_opts;
+    ASSERT_TRUE(iterator.init(iter_opts).ok());
+    
iterator.set_read_requirement(ColumnIterator::ReadRequirement::LAZY_OUTPUT);
+    iterator.set_read_phase(ColumnIterator::ReadPhase::PREDICATE);
+
+    MutableColumnPtr dst = ColumnInt32::create();
+    size_t rows = 3;
+    bool has_null = false;
+    ASSERT_TRUE(iterator.next_batch(&rows, dst, &has_null).ok());
+    EXPECT_TRUE(iterator._has_place_holder_column);
+
+    IColumn::Filter filter;
+    filter.resize(3);
+    filter[0] = 1;
+    filter[1] = 0;
+    filter[2] = 1;
+    dst = IColumn::mutate(dst->filter(filter, 2));
+    ASSERT_EQ(2, dst->size());
+
+    iterator.set_read_phase(ColumnIterator::ReadPhase::LAZY);
+    if (read_by_rowids) {
+        const rowid_t rowids[] = {2, 8};
+        ASSERT_TRUE(iterator.read_by_rowids(rowids, std::size(rowids), 
dst).ok());
+    } else {
+        rows = 2;
+        ASSERT_TRUE(iterator.next_batch(&rows, dst, &has_null).ok());
+    }
+
+    ASSERT_EQ(2, dst->size());
+    EXPECT_FALSE(iterator._has_place_holder_column);
+    const auto& result = assert_cast<const ColumnInt32&>(*dst);
+    EXPECT_EQ(7, result.get_element(0));
+    EXPECT_EQ(7, result.get_element(1));
+}
+
+void check_default_value_predicate_not_read_again(bool read_by_rowids) {
+    SCOPED_TRACE(read_by_rowids ? "read_by_rowids" : "next_batch");
+
+    DefaultValueColumnIterator iterator(true, "7", false, 
FieldType::OLAP_FIELD_TYPE_INT, 0, 0,
+                                        sizeof(int32_t));
+    ColumnIteratorOptions iter_opts;
+    ASSERT_TRUE(iterator.init(iter_opts).ok());
+    iterator.set_read_requirement(ColumnIterator::ReadRequirement::PREDICATE);
+    iterator.set_read_phase(ColumnIterator::ReadPhase::PREDICATE);
+
+    MutableColumnPtr dst = ColumnInt32::create();
+    size_t rows = 3;
+    bool has_null = true;
+    ASSERT_TRUE(iterator.next_batch(&rows, dst, &has_null).ok());
+    EXPECT_FALSE(has_null);
+    EXPECT_FALSE(iterator._has_place_holder_column);
+
+    IColumn::Filter filter;
+    filter.resize(3);
+    filter[0] = 1;
+    filter[1] = 0;
+    filter[2] = 1;
+    dst = IColumn::mutate(dst->filter(filter, 2));
+    ASSERT_EQ(2, dst->size());
+
+    iterator.set_read_phase(ColumnIterator::ReadPhase::LAZY);
+    if (read_by_rowids) {
+        const rowid_t rowids[] = {2, 8};
+        ASSERT_TRUE(iterator.read_by_rowids(rowids, std::size(rowids), 
dst).ok());
+    } else {
+        rows = 2;
+        ASSERT_TRUE(iterator.next_batch(&rows, dst, &has_null).ok());
+    }
+
+    ASSERT_EQ(2, dst->size());
+    const auto& result = assert_cast<const ColumnInt32&>(*dst);
+    EXPECT_EQ(7, result.get_element(0));
+    EXPECT_EQ(7, result.get_element(1));
+}
+} // namespace
+
+TEST_F(ColumnReaderTest, DefaultValueLazyOutputRecoversFilteredPlaceholder) {
+    check_default_value_lazy_output(false);
+    check_default_value_lazy_output(true);
+}
+
+TEST_F(ColumnReaderTest, DefaultValueLazyOutputFinalizesEmptySelection) {
+    DefaultValueColumnIterator iterator(true, "7", false, 
FieldType::OLAP_FIELD_TYPE_INT, 0, 0,
+                                        sizeof(int32_t));
+    ColumnIteratorOptions iter_opts;
+    ASSERT_TRUE(iterator.init(iter_opts).ok());
+    
iterator.set_read_requirement(ColumnIterator::ReadRequirement::LAZY_OUTPUT);
+    iterator.set_read_phase(ColumnIterator::ReadPhase::PREDICATE);
+
+    MutableColumnPtr dst = ColumnInt32::create();
+    size_t rows = 3;
+    bool has_null = false;
+    ASSERT_TRUE(iterator.next_batch(&rows, dst, &has_null).ok());
+    EXPECT_TRUE(iterator._has_place_holder_column);
+
+    IColumn::Filter filter;
+    filter.resize(3);
+    filter[0] = 0;
+    filter[1] = 0;
+    filter[2] = 0;
+    dst = IColumn::mutate(dst->filter(filter, 0));
+    ASSERT_EQ(0, dst->size());
+
+    iterator.set_read_phase(ColumnIterator::ReadPhase::LAZY);
+    iterator.finalize_lazy_phase(dst);
+    EXPECT_EQ(0, dst->size());
+    EXPECT_FALSE(iterator._has_place_holder_column);
+}
+
+TEST_F(ColumnReaderTest, DefaultValuePredicateIsNotReadAgainInLazyPhase) {
+    check_default_value_predicate_not_read_again(false);
+    check_default_value_predicate_not_read_again(true);
+}
+
 TEST_F(ColumnReaderTest, SetReadRequirementPropagatesToNestedIterators) {
     auto null_iter = 
std::make_unique<FileColumnIterator>(std::make_shared<ColumnReader>());
     std::vector<ColumnIteratorUPtr> struct_sub_iters;
diff --git 
a/regression-test/data/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.out
 
b/regression-test/data/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.out
new file mode 100644
index 00000000000..55de0e75c37
--- /dev/null
+++ 
b/regression-test/data/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.out
@@ -0,0 +1,13 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !default_value_as_lazy_output --
+\N
+\N
+\N
+90
+
+-- !default_value_as_predicate --
+0
+1
+2
+7
+
diff --git 
a/regression-test/suites/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.groovy
 
b/regression-test/suites/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.groovy
new file mode 100644
index 00000000000..9797453e901
--- /dev/null
+++ 
b/regression-test/suites/datatype_p0/complex_types/test_light_schema_change_lazy_pruned_struct.groovy
@@ -0,0 +1,95 @@
+// 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.
+
+suite("test_light_schema_change_lazy_pruned_struct") {
+    sql "set batch_size = 4"
+    sql "set enable_prune_nested_column = true"
+
+    sql "DROP TABLE IF EXISTS test_light_schema_change_lazy_pruned_struct"
+    sql """
+        CREATE TABLE test_light_schema_change_lazy_pruned_struct (
+            id INT,
+            s STRUCT<b:INT, c:VARCHAR(20)>,
+            b INT,
+            a INT
+        )
+        DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "light_schema_change" = "true",
+            "disable_auto_compaction" = "true"
+        )
+    """
+
+    // Keep these rows in the old physical schema. After the ALTER, s.a is 
read by
+    // DefaultValueColumnIterator for this rowset.
+    sql """
+        INSERT INTO test_light_schema_change_lazy_pruned_struct VALUES
+            (1, named_struct('b', 1, 'c', 'abc'), 21, 11),
+            (2, named_struct('b', 2, 'c', 'abc'), 22, 12),
+            (3, named_struct('b', 0, 'c', 'abc'), 23, 13),
+            (4, named_struct('b', 4, 'c', 'def'), 24, 14),
+            (5, named_struct('b', 5, 'c', 'abc'), 20, 15),
+            (6, named_struct('b', 6, 'c', 'abc'), 26, 10),
+            (7, named_struct('b', 7, 'c', 'abc'), 27, 17),
+            (8, named_struct('b', 8, 'c', 'def'), 28, 18)
+    """
+
+    sql """
+        ALTER TABLE test_light_schema_change_lazy_pruned_struct
+        MODIFY COLUMN s STRUCT<b:INT, c:VARCHAR(20), a:INT>
+    """
+    waitForSchemaChangeDone {
+        sql """
+            SHOW ALTER TABLE COLUMN
+            WHERE TableName='test_light_schema_change_lazy_pruned_struct'
+            ORDER BY CreateTime DESC LIMIT 1
+        """
+        time 600
+    }
+
+    // Also keep a rowset written with the new physical schema in the same 
scan.
+    sql """
+        INSERT INTO test_light_schema_change_lazy_pruned_struct VALUES
+            (9, named_struct('b', 9, 'c', 'abc', 'a', 90), 29, 19),
+            (10, named_struct('b', 0, 'c', 'abc', 'a', 100), 30, 20)
+    """
+
+    // s.a is a lazy output column, while s.b and s.c are predicate columns.
+    // On old rowsets s.a must replace its predicate-phase placeholders 
instead of
+    // appending defaults to them during lazy materialization.
+    order_qt_default_value_as_lazy_output """
+        SELECT s.a
+        FROM test_light_schema_change_lazy_pruned_struct
+        WHERE s.b > 0
+          AND s.c = 'abc'
+          AND b > 20
+          AND a > 10
+    """
+
+    // Exercise the inverse phase assignment: the default-backed child is used 
by
+    // a predicate and an old physical child is materialized lazily.
+    order_qt_default_value_as_predicate """
+        SELECT s.b
+        FROM test_light_schema_change_lazy_pruned_struct
+        WHERE s.a IS NULL
+          AND s.c = 'abc'
+          AND b > 20
+          AND a > 10
+    """
+}


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

Reply via email to