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 be342d566f6 [fix](exec) preserve NULL in pushed-down CHAR MIN/MAX 
(#65952)
be342d566f6 is described below

commit be342d566f60dd5b81651d58ff1a4303324f578b
Author: Mryange <[email protected]>
AuthorDate: Fri Aug 7 18:39:34 2026 +0800

    [fix](exec) preserve NULL in pushed-down CHAR MIN/MAX (#65952)
    
    ### What problem does this PR solve?
    
    Problem Summary: `MIN` on a nullable CHAR column could return an empty
    string when string min/max aggregation was pushed down to multiple
    tablets. The zone-map reader produced NULL correctly, but the CHAR
    padding cleanup rebuilt each value through a non-null `Field`,
    converting `StringRef{nullptr, 0}` into an empty string. Preserve the
    original StringRef through `insert_data()` so NULL remains NULL while
    CHAR padding is still removed.
    
    ### Release note
    
    Fix incorrect `MIN` results for nullable CHAR columns when min/max
    aggregation pushdown is enabled.
    
    ### 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/storage/iterator/vgeneric_iterators.cpp |  4 +-
 be/test/exec/scan/vgeneric_iterators_test.cpp  | 79 ++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/be/src/storage/iterator/vgeneric_iterators.cpp 
b/be/src/storage/iterator/vgeneric_iterators.cpp
index 13e6040b799..356744ce785 100644
--- a/be/src/storage/iterator/vgeneric_iterators.cpp
+++ b/be/src/storage/iterator/vgeneric_iterators.cpp
@@ -80,8 +80,8 @@ Status VStatisticsIterator::next_batch(Block* block) {
                     _schema.column(cid)->type() == 
FieldType::OLAP_FIELD_TYPE_CHAR) {
                     auto col = columns[i]->clone_empty();
                     for (size_t j = 0; j < columns[i]->size(); ++j) {
-                        const auto& ref = 
columns[i]->get_data_at(j).trim_tail_padding_zero();
-                        
col->insert(Field::create_field<TYPE_CHAR>(ref.to_string()));
+                        const auto ref = 
columns[i]->get_data_at(j).trim_tail_padding_zero();
+                        col->insert_data(ref.data, ref.size);
                     }
                     columns[i].swap(col);
                 }
diff --git a/be/test/exec/scan/vgeneric_iterators_test.cpp 
b/be/test/exec/scan/vgeneric_iterators_test.cpp
index d461512c4e0..71b71847b86 100644
--- a/be/test/exec/scan/vgeneric_iterators_test.cpp
+++ b/be/test/exec/scan/vgeneric_iterators_test.cpp
@@ -23,15 +23,23 @@
 #include <memory>
 #include <vector>
 
+#include "core/assert_cast.h"
 #include "core/block/column_with_type_and_name.h"
 #include "core/column/column.h"
+#include "core/column/column_nullable.h"
 #include "core/data_type/data_type.h"
 #include "core/field.h"
 #include "gtest/gtest_pred_impl.h"
+#include "io/fs/file_writer.h"
+#include "io/fs/local_file_system.h"
 #include "storage/olap_common.h"
+#include "storage/row_cursor.h"
 #include "storage/schema.h"
 #include "storage/segment/column_reader.h"
+#include "storage/segment/segment.h"
+#include "storage/segment/test_segment_writer.h"
 #include "storage/tablet/tablet_schema.h"
+#include "storage/tablet/tablet_schema_helper.h"
 
 namespace doris {
 using namespace ErrorCode;
@@ -112,6 +120,77 @@ TEST(VGenericIteratorsTest, AutoIncrement) {
     }
 }
 
+TEST(VGenericIteratorsTest, StatisticsIteratorPreservesNullForNullableChar) {
+    constexpr auto test_dir = "./ut_dir/vgeneric_iterators_test";
+    constexpr auto segment_path = 
"./ut_dir/vgeneric_iterators_test/nullable_char_segment.dat";
+    constexpr auto row_count = 3;
+
+    auto fs = io::global_local_filesystem();
+    ASSERT_TRUE(fs->delete_directory(test_dir).ok());
+    ASSERT_TRUE(fs->create_directory(test_dir).ok());
+
+    auto tablet_schema = std::make_shared<TabletSchema>();
+    tablet_schema->append_column(*create_int_key(0, false));
+    auto nullable_char = std::make_shared<TabletColumn>();
+    nullable_char->set_unique_id(1);
+    nullable_char->set_name("1");
+    nullable_char->set_type(FieldType::OLAP_FIELD_TYPE_CHAR);
+    nullable_char->set_is_nullable(true);
+    nullable_char->set_length(8);
+    nullable_char->set_index_length(8);
+    
nullable_char->set_aggregation_method(FieldAggregationMethod::OLAP_FIELD_AGGREGATION_NONE);
+    tablet_schema->append_column(*nullable_char);
+    tablet_schema->set_storage_page_size(4096);
+
+    io::FileWriterPtr file_writer;
+    ASSERT_TRUE(fs->create_file(segment_path, &file_writer).ok());
+    SegmentWriterOptions writer_options;
+    writer_options.num_rows_per_block = 1024;
+    TestSegmentWriter writer(file_writer.get(), 0, tablet_schema, nullptr, 
nullptr, writer_options,
+                             nullptr);
+    ASSERT_TRUE(writer.init().ok());
+
+    RowCursor row;
+    std::vector<Field> fields(tablet_schema->num_columns(), 
Field(PrimitiveType::TYPE_NULL));
+    ASSERT_TRUE(row.init_scan_key(tablet_schema, std::move(fields)).ok());
+    for (int i = 0; i < row_count; ++i) {
+        row.mutable_field(0) = Field::create_field<TYPE_INT>(i);
+        ASSERT_TRUE(writer.append_row(row).ok());
+    }
+    uint64_t file_size = 0;
+    uint64_t index_size = 0;
+    ASSERT_TRUE(writer.finalize(&file_size, &index_size).ok());
+    ASSERT_TRUE(file_writer->close().ok());
+
+    std::shared_ptr<segment_v2::Segment> segment;
+    ASSERT_TRUE(segment_v2::Segment::open(fs, segment_path, 100, 0, RowsetId 
{.version = 1},
+                                          tablet_schema, io::FileReaderOptions 
{}, &segment)
+                        .ok());
+
+    std::vector<ColumnId> column_ids {0, 1};
+    Schema schema(tablet_schema->columns(), column_ids);
+    VStatisticsIterator iterator(segment, schema);
+    StorageReadOptions read_options;
+    OlapReaderStatistics stats;
+    read_options.push_down_agg_type_opt = TPushAggOp::MINMAX;
+    read_options.stats = &stats;
+    read_options.tablet_schema = tablet_schema;
+    ASSERT_TRUE(iterator.init(read_options).ok());
+
+    Block block;
+    create_block(schema, block);
+    ASSERT_TRUE(iterator.next_batch(&block).ok());
+    ASSERT_EQ(2, block.rows());
+
+    const auto& nullable_column =
+            assert_cast<const 
ColumnNullable&>(*block.get_by_position(1).column);
+    EXPECT_TRUE(nullable_column.is_null_at(0));
+    EXPECT_TRUE(nullable_column.is_null_at(1));
+    ASSERT_TRUE(iterator.next_batch(&block).is<ErrorCode::END_OF_FILE>());
+
+    ASSERT_TRUE(fs->delete_directory(test_dir).ok());
+}
+
 TEST(VGenericIteratorsTest, Union) {
     auto schema = create_schema();
     auto output_schema = std::make_shared<Schema>(schema);


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

Reply via email to