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 50aa052ad95 [refactor](iterator) Clean up VStatisticsIterator and move 
VAutoIncrementIterator into test (#69627)
50aa052ad95 is described below

commit 50aa052ad9555fcdd31913584f090608faf35fa7
Author: Chenyang Sun <[email protected]>
AuthorDate: Tue Oct 27 10:49:43 2026 +0800

    [refactor](iterator) Clean up VStatisticsIterator and move 
VAutoIncrementIterator into test (#69627)
    
    Remove `_column_iterators_map` from VStatisticsIterator and drop its
    CHAR padding trim code.
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 be/src/core/string_ref.cpp                     |  11 --
 be/src/core/string_ref.h                       |   1 -
 be/src/storage/iterator/vgeneric_iterators.cpp | 111 +-------------
 be/src/storage/iterator/vgeneric_iterators.h   |  10 +-
 be/test/exec/scan/vgeneric_iterators_test.cpp  | 192 ++++++++++++++++++++-----
 5 files changed, 164 insertions(+), 161 deletions(-)

diff --git a/be/src/core/string_ref.cpp b/be/src/core/string_ref.cpp
index 6428d6968e0..849de48c9b4 100644
--- a/be/src/core/string_ref.cpp
+++ b/be/src/core/string_ref.cpp
@@ -40,17 +40,6 @@ StringRef StringRef::trim() const {
     return StringRef(data + begin, end - begin + 1);
 }
 
-StringRef StringRef::trim_tail_padding_zero() const {
-    // Remove trailing padding zero.
-    int64_t end = size - 1;
-
-    while (end >= 0 && data[end] == '\0') {
-        --end;
-    }
-
-    return StringRef(data, end + 1);
-}
-
 StringRef StringRef::trim_whitespace() const {
     // Remove leading and trailing whitespace.
     int64_t begin = 0;
diff --git a/be/src/core/string_ref.h b/be/src/core/string_ref.h
index cf9cbcf3a37..56764409685 100644
--- a/be/src/core/string_ref.h
+++ b/be/src/core/string_ref.h
@@ -218,7 +218,6 @@ struct StringRef {
 
     // Trims leading and trailing spaces.
     StringRef trim() const;
-    StringRef trim_tail_padding_zero() const;
     StringRef trim_whitespace() const;
     StringRef trim_quote() const;
 
diff --git a/be/src/storage/iterator/vgeneric_iterators.cpp 
b/be/src/storage/iterator/vgeneric_iterators.cpp
index b3c7358f418..eacfbbb3df0 100644
--- a/be/src/storage/iterator/vgeneric_iterators.cpp
+++ b/be/src/storage/iterator/vgeneric_iterators.cpp
@@ -44,14 +44,12 @@ Status VStatisticsIterator::init(const StorageReadOptions& 
opts) {
     if (!_init) {
         _push_down_agg_type_opt = opts.push_down_agg_type_opt;
 
-        for (size_t ordinal = 0; ordinal < _schema.num_block_columns(); 
ordinal++) {
-            const auto* column_desc = _schema.column(ordinal);
-            auto unique_id = column_desc->unique_id();
-            if (_column_iterators_map.count(unique_id) < 1) {
-                RETURN_IF_ERROR(_segment->new_column_iterator(
-                        *column_desc, &_column_iterators_map[unique_id], 
&opts));
+        if (_push_down_agg_type_opt != TPushAggOp::COUNT) {
+            _column_iterators.resize(_schema.num_block_columns());
+            for (size_t ordinal = 0; ordinal < _schema.num_block_columns(); 
++ordinal) {
+                
RETURN_IF_ERROR(_segment->new_column_iterator(*_schema.column(ordinal),
+                                                              
&_column_iterators[ordinal], &opts));
             }
-            
_column_iterators.push_back(_column_iterators_map[unique_id].get());
         }
 
         _target_rows = _push_down_agg_type_opt == TPushAggOp::MINMAX ? 2 : 
_segment->num_rows();
@@ -62,7 +60,7 @@ Status VStatisticsIterator::init(const StorageReadOptions& 
opts) {
 }
 
 Status VStatisticsIterator::next_batch(Block* block) {
-    DCHECK_EQ(block->columns(), _column_iterators.size());
+    DCHECK_EQ(block->columns(), _schema.num_block_columns());
     if (_output_rows < _target_rows) {
         block->clear_column_data();
         auto columns_guard = block->mutate_columns_scoped();
@@ -76,16 +74,8 @@ Status VStatisticsIterator::next_batch(Block* block) {
                 column->insert_many_defaults(size);
             }
         } else {
-            for (int i = 0; i < columns.size(); ++i) {
+            for (size_t i = 0; i < columns.size(); ++i) {
                 
RETURN_IF_ERROR(_column_iterators[i]->next_batch_of_zone_map(&size, 
columns[i]));
-                if (_schema.column(i)->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_data(ref.data, ref.size);
-                    }
-                    columns[i].swap(col);
-                }
             }
         }
         _output_rows += size;
@@ -192,89 +182,6 @@ Status VMergeIteratorContext::copy_rows(BlockView* view, 
bool advanced) {
     return Status::OK();
 }
 
-// This iterator will generate ordered data. For example for schema
-// (int, int) this iterator will generator data like
-// (0, 1), (1, 2), (2, 3), (3, 4)...
-//
-// Usage:
-//      ReadSchema schema;
-//      VAutoIncrementIterator iter(schema, 1000);
-//      StorageReadOptions opts;
-//      RETURN_IF_ERROR(iter.init(opts));
-//      Block block;
-//      do {
-//          st = iter.next_batch(&block);
-//      } while (st.ok());
-class VAutoIncrementIterator : public RowwiseIterator {
-public:
-    // Will generate num_rows rows in total
-    VAutoIncrementIterator(const ReadSchema& schema, size_t num_rows)
-            : _schema(schema), _num_rows(num_rows), _rows_returned() {}
-    ~VAutoIncrementIterator() override = default;
-
-    // NOTE: Currently, this function will ignore StorageReadOptions
-    Status init(const StorageReadOptions& opts) override;
-
-    Status next_batch(Block* block) override {
-        int row_idx = 0;
-        while (_rows_returned < _num_rows) {
-            for (int j = 0; j < _schema.num_block_columns(); ++j) {
-                ColumnWithTypeAndName& vc = block->get_by_position(j);
-                IColumn& vi = (IColumn&)(*vc.column);
-
-                char data[16] = {};
-                size_t data_len = 0;
-                const auto* col_schema = _schema.column(j);
-                switch (col_schema->type()) {
-                case FieldType::OLAP_FIELD_TYPE_SMALLINT:
-                    *(int16_t*)data = cast_set<int16_t>(_rows_returned + j);
-                    data_len = sizeof(int16_t);
-                    break;
-                case FieldType::OLAP_FIELD_TYPE_INT:
-                    *(int32_t*)data = cast_set<int32_t>(_rows_returned + j);
-                    data_len = sizeof(int32_t);
-                    break;
-                case FieldType::OLAP_FIELD_TYPE_BIGINT:
-                    *(int64_t*)data = cast_set<int64_t>(_rows_returned + j);
-                    data_len = sizeof(int64_t);
-                    break;
-                case FieldType::OLAP_FIELD_TYPE_FLOAT:
-                    *(float*)data = cast_set<float>(_rows_returned + j);
-                    data_len = sizeof(float);
-                    break;
-                case FieldType::OLAP_FIELD_TYPE_DOUBLE:
-                    *(double*)data = cast_set<double>(_rows_returned + j);
-                    data_len = sizeof(double);
-                    break;
-                default:
-                    break;
-                }
-
-                vi.insert_data(data, data_len);
-            }
-
-            ++row_idx;
-            ++_rows_returned;
-        }
-
-        if (row_idx > 0) {
-            return Status::OK();
-        }
-        return Status::EndOfFile("End of VAutoIncrementIterator");
-    }
-
-    const ReadSchema& schema() const override { return _schema; }
-
-private:
-    const ReadSchema& _schema;
-    size_t _num_rows;
-    size_t _rows_returned;
-};
-
-Status VAutoIncrementIterator::init(const StorageReadOptions& opts) {
-    return Status::OK();
-}
-
 Status VMergeIteratorContext::init(const StorageReadOptions& opts) {
     _block_row_max = opts.block_row_max;
     RETURN_IF_ERROR(_load_next_block());
@@ -488,8 +395,4 @@ RowwiseIterator* 
new_vstatistics_iterator(std::shared_ptr<Segment> segment,
     return new VStatisticsIterator(segment, schema);
 }
 
-RowwiseIteratorUPtr new_auto_increment_iterator(const ReadSchema& schema, 
size_t num_rows) {
-    return std::make_unique<VAutoIncrementIterator>(schema, num_rows);
-}
-
 } // namespace doris
diff --git a/be/src/storage/iterator/vgeneric_iterators.h 
b/be/src/storage/iterator/vgeneric_iterators.h
index 876bb620aa9..cc416d9efea 100644
--- a/be/src/storage/iterator/vgeneric_iterators.h
+++ b/be/src/storage/iterator/vgeneric_iterators.h
@@ -21,7 +21,6 @@
 #include <stdint.h>
 
 #include <list>
-#include <map>
 #include <memory>
 #include <queue>
 #include <utility>
@@ -64,8 +63,7 @@ private:
     size_t _output_rows = 0;
     bool _init = false;
     TPushAggOp::type _push_down_agg_type_opt;
-    std::map<int32_t, std::unique_ptr<ColumnIterator>> _column_iterators_map;
-    std::vector<ColumnIterator*> _column_iterators;
+    std::vector<std::unique_ptr<ColumnIterator>> _column_iterators;
 
     static constexpr size_t MAX_ROW_SIZE_IN_COUNT = 65535;
 };
@@ -325,12 +323,6 @@ RowwiseIteratorUPtr 
new_merge_iterator(std::vector<RowwiseIteratorUPtr>&& inputs
 RowwiseIteratorUPtr new_union_iterator(std::vector<RowwiseIteratorUPtr>&& 
inputs,
                                        ReadSchemaSPtr read_schema);
 
-// Create an auto increment iterator which returns num_rows data in format of 
schema.
-// This class aims to be used in unit test.
-//
-// Client should delete returned iterator.
-RowwiseIteratorUPtr new_auto_increment_iterator(const ReadSchema& schema, 
size_t num_rows);
-
 RowwiseIterator* new_vstatistics_iterator(std::shared_ptr<Segment> segment,
                                           const ReadSchema& schema);
 
diff --git a/be/test/exec/scan/vgeneric_iterators_test.cpp 
b/be/test/exec/scan/vgeneric_iterators_test.cpp
index 6e91f5a6941..187e8b486dd 100644
--- a/be/test/exec/scan/vgeneric_iterators_test.cpp
+++ b/be/test/exec/scan/vgeneric_iterators_test.cpp
@@ -22,8 +22,11 @@
 
 #include <cstring>
 #include <memory>
+#include <optional>
+#include <string>
 #include <vector>
 
+#include "common/cast_set.h"
 #include "core/assert_cast.h"
 #include "core/block/column_with_type_and_name.h"
 #include "core/column/column.h"
@@ -50,6 +53,73 @@
 namespace doris {
 using namespace ErrorCode;
 
+namespace {
+
+// Generates `num_rows` ordered rows in the layout of `schema`: row r holds r 
+ j in column j, so
+// (int, int) gives (0, 1), (1, 2), (2, 3), ... It hands every row out in its 
first next_batch()
+// and ignores the StorageReadOptions; the tests below rely on both.
+class AutoIncrementUtIterator : public RowwiseIterator {
+public:
+    AutoIncrementUtIterator(const ReadSchema& schema, size_t num_rows)
+            : _schema(schema), _num_rows(num_rows) {}
+    ~AutoIncrementUtIterator() override = default;
+
+    Status init(const StorageReadOptions& opts) override { return 
Status::OK(); }
+
+    Status next_batch(Block* block) override {
+        if (_rows_returned == _num_rows) {
+            return Status::EndOfFile("End of AutoIncrementUtIterator");
+        }
+        auto columns_guard = block->mutate_columns_scoped();
+        auto& columns = columns_guard.mutable_columns();
+        for (; _rows_returned < _num_rows; ++_rows_returned) {
+            for (size_t j = 0; j < _schema.num_block_columns(); ++j) {
+                char data[16] = {};
+                size_t data_len = 0;
+                switch (_schema.column(j)->type()) {
+                case FieldType::OLAP_FIELD_TYPE_SMALLINT:
+                    *(int16_t*)data = cast_set<int16_t>(_rows_returned + j);
+                    data_len = sizeof(int16_t);
+                    break;
+                case FieldType::OLAP_FIELD_TYPE_INT:
+                    *(int32_t*)data = cast_set<int32_t>(_rows_returned + j);
+                    data_len = sizeof(int32_t);
+                    break;
+                case FieldType::OLAP_FIELD_TYPE_BIGINT:
+                    *(int64_t*)data = cast_set<int64_t>(_rows_returned + j);
+                    data_len = sizeof(int64_t);
+                    break;
+                case FieldType::OLAP_FIELD_TYPE_FLOAT:
+                    *(float*)data = cast_set<float>(_rows_returned + j);
+                    data_len = sizeof(float);
+                    break;
+                case FieldType::OLAP_FIELD_TYPE_DOUBLE:
+                    *(double*)data = cast_set<double>(_rows_returned + j);
+                    data_len = sizeof(double);
+                    break;
+                default:
+                    break;
+                }
+                columns[j]->insert_data(data, data_len);
+            }
+        }
+        return Status::OK();
+    }
+
+    const ReadSchema& schema() const override { return _schema; }
+
+private:
+    const ReadSchema& _schema;
+    size_t _num_rows;
+    size_t _rows_returned = 0;
+};
+
+RowwiseIteratorUPtr new_auto_increment_iterator(const ReadSchema& schema, 
size_t num_rows) {
+    return std::make_unique<AutoIncrementUtIterator>(schema, num_rows);
+}
+
+} // namespace
+
 class VGenericIteratorsTest : public testing::Test {
 public:
     VGenericIteratorsTest() {}
@@ -119,17 +189,15 @@ 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;
-
+// Writes one segment of an INT key and a nullable CHAR(8) column holding 
`values`, std::nullopt
+// for NULL, and opens it. The writer pads every CHAR value to the declared 8 
bytes, which is what
+// the segment's zone map keeps as its bounds.
+static std::shared_ptr<segment_v2::Segment> write_char_segment(
+        const std::string& segment_path, const 
std::vector<std::optional<std::string>>& values,
+        TabletSchemaSPtr* tablet_schema) {
     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));
+    *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");
@@ -138,55 +206,107 @@ TEST(VGenericIteratorsTest, 
StatisticsIteratorPreservesNullForNullableChar) {
     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);
+    (*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());
+    EXPECT_TRUE(fs->create_file(segment_path, &file_writer).ok());
     VerticalSegmentWriterOptions writer_options;
     writer_options.num_rows_per_block = 1024;
-    TestVerticalSegmentWriter writer(file_writer.get(), 0, tablet_schema, 
nullptr, nullptr,
+    TestVerticalSegmentWriter writer(file_writer.get(), 0, *tablet_schema, 
nullptr, nullptr,
                                      writer_options, nullptr);
-    ASSERT_TRUE(writer.init().ok());
+    EXPECT_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());
+    std::vector<Field> fields((*tablet_schema)->num_columns(), 
Field(PrimitiveType::TYPE_NULL));
+    EXPECT_TRUE(row.init_scan_key(*tablet_schema, std::move(fields)).ok());
+    for (size_t i = 0; i < values.size(); ++i) {
+        row.mutable_field(0) = 
Field::create_field<TYPE_INT>(static_cast<int32_t>(i));
+        row.mutable_field(1) = values[i].has_value()
+                                       ? 
Field::create_field<TYPE_STRING>(String(*values[i]))
+                                       : Field(PrimitiveType::TYPE_NULL);
+        EXPECT_TRUE(writer.append_row(row).ok());
     }
     uint64_t file_size = 0;
     uint64_t index_size = 0;
-    ASSERT_TRUE(writer.finalize_columns(&index_size).ok());
-    ASSERT_TRUE(writer.finalize_footer(&file_size).ok());
-    ASSERT_TRUE(file_writer->close().ok());
+    EXPECT_TRUE(writer.finalize_columns(&index_size).ok());
+    EXPECT_TRUE(writer.finalize_footer(&file_size).ok());
+    EXPECT_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)
+    EXPECT_TRUE(segment_v2::Segment::open(fs, segment_path, 100, 0, RowsetId 
{.version = 1},
+                                          *tablet_schema, 
io::FileReaderOptions {}, &segment)
                         .ok());
+    return segment;
+}
 
-    std::vector<ColumnId> column_ids {0, 1};
-    ReadSchema schema(project_columns_by_ordinal(tablet_schema->columns(), 
column_ids));
-    VStatisticsIterator iterator(segment, schema);
-    OlapReaderStatistics stats;
-    StorageReadOptions read_options(stats);
-    read_options.push_down_agg_type_opt = TPushAggOp::MINMAX;
-    ASSERT_TRUE(iterator.init(read_options).ok());
+class StatisticsIteratorCharTest : public testing::Test {
+protected:
+    static constexpr auto kTestDir = "./ut_dir/vgeneric_iterators_test";
+
+    void SetUp() override {
+        _fs = io::global_local_filesystem();
+        ASSERT_TRUE(_fs->delete_directory(kTestDir).ok());
+        ASSERT_TRUE(_fs->create_directory(kTestDir).ok());
+    }
+    void TearDown() override { 
EXPECT_TRUE(_fs->delete_directory(kTestDir).ok()); }
+
+    // Answers the pushed-down `agg` over a segment holding `values` in the 
CHAR column: the
+    // first Block it hands out goes to `block`, and the read must be over 
after it.
+    void read_statistics(const std::vector<std::optional<std::string>>& values,
+                         TPushAggOp::type agg, Block* block) {
+        TabletSchemaSPtr tablet_schema;
+        auto segment = write_char_segment(std::string(kTestDir) + 
"/char_segment.dat", values,
+                                          &tablet_schema);
+        ASSERT_NE(segment, nullptr);
+        std::vector<ColumnId> column_ids {0, 1};
+        ReadSchema schema(project_columns_by_ordinal(tablet_schema->columns(), 
column_ids));
+        std::unique_ptr<RowwiseIterator> iterator =
+                std::make_unique<VStatisticsIterator>(segment, schema);
+        OlapReaderStatistics stats;
+        StorageReadOptions read_options(stats);
+        read_options.push_down_agg_type_opt = agg;
+        ASSERT_TRUE(iterator->init(read_options).ok());
+
+        create_block(schema, *block);
+        ASSERT_TRUE(iterator->next_batch(block).ok());
+        Block after_last;
+        create_block(schema, after_last);
+        
ASSERT_TRUE(iterator->next_batch(&after_last).is<ErrorCode::END_OF_FILE>());
+    }
+
+    std::shared_ptr<io::FileSystem> _fs;
+};
 
+TEST_F(StatisticsIteratorCharTest, PreservesNullForNullableChar) {
     Block block;
-    create_block(schema, block);
-    ASSERT_TRUE(iterator.next_batch(&block).ok());
+    read_statistics({std::nullopt, std::nullopt, std::nullopt}, 
TPushAggOp::MINMAX, &block);
     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());
+// CHAR(8) stores "a" as "a" plus seven '\0'. MIN/MAX answered from the zone 
map must come out as
+// the rows of a normal read do: without the padding.
+TEST_F(StatisticsIteratorCharTest, AnswersCharBoundsWithoutPadding) {
+    Block block;
+    read_statistics({"bb", "a", "ccc"}, TPushAggOp::MINMAX, &block);
+    ASSERT_EQ(2, block.rows());
+
+    // next_batch_of_zone_map hands out the max first, then the min.
+    const auto& char_column = *block.get_by_position(1).column;
+    EXPECT_EQ("ccc", char_column.get_data_at(0).to_string());
+    EXPECT_EQ("a", char_column.get_data_at(1).to_string());
+}
+
+// COUNT reports the segment's row count and reads no zone map, so every row 
is a default.
+TEST_F(StatisticsIteratorCharTest, CountFillsTheRowCount) {
+    Block block;
+    read_statistics({"bb", std::nullopt, "ccc"}, TPushAggOp::COUNT, &block);
+    EXPECT_EQ(3, block.rows());
 }
 
 // A string zone map bound is cut to 512 bytes, and a cut bound is not a value 
the column holds:


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

Reply via email to