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]