This is an automated email from the ASF dual-hosted git repository.
Gabriel39 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 b440a3f6f94 [fix](be) Harden FileScannerV2 filtering and profiling
(#65624)
b440a3f6f94 is described below
commit b440a3f6f9408fa99c8e0fc2ec99d5a71ec09361
Author: Gabriel <[email protected]>
AuthorDate: Thu Jul 16 10:14:53 2026 +0800
[fix](be) Harden FileScannerV2 filtering and profiling (#65624)
FileScannerV2 had several correctness,
fallback-contract, lazy-materialization, profiling, and repeated
schema-work gaps. Scanner slot ids could be mistaken for table-global
ordinals, invalid Parquet dictionary ids could silently reject rows,
dictionary selection could request a clean fallback after advancing the
stream, filters that could not be localized still forced eager reads,
and every split counted all row groups in the file. Equality-delete
loading also rebuilt its schema-shaped block for each batch.
---
be/src/exec/scan/file_scanner_v2.cpp | 10 ++--
be/src/format_v2/column_mapper.cpp | 32 +++++++++++++
be/src/format_v2/parquet/parquet_statistics.cpp | 10 ++--
.../parquet/reader/scalar_column_reader.cpp | 25 ++++++++--
be/src/format_v2/table/iceberg_reader.cpp | 17 +++----
be/test/exec/scan/file_scanner_v2_test.cpp | 11 ++---
be/test/format_v2/column_mapper_test.cpp | 39 ++++++++--------
.../parquet/parquet_reader_control_test.cpp | 54 ++++++++++++++++++++++
be/test/format_v2/parquet/parquet_reader_test.cpp | 2 +-
be/test/format_v2/parquet/parquet_scan_test.cpp | 2 +-
be/test/format_v2/table_reader_test.cpp | 9 ++--
11 files changed, 152 insertions(+), 59 deletions(-)
diff --git a/be/src/exec/scan/file_scanner_v2.cpp
b/be/src/exec/scan/file_scanner_v2.cpp
index d1c0edd54b1..99586b5cfa9 100644
--- a/be/src/exec/scan/file_scanner_v2.cpp
+++ b/be/src/exec/scan/file_scanner_v2.cpp
@@ -197,13 +197,9 @@ Status rewrite_slot_refs_to_global_index(
const auto* slot_ref = assert_cast<const VSlotRef*>(expr->get());
const auto global_index_it =
slot_id_to_global_index.find(slot_ref->slot_id());
if (global_index_it == slot_id_to_global_index.end()) {
- DORIS_CHECK(slot_ref->slot_id() >= 0);
- const auto global_index =
format::GlobalIndex(cast_set<size_t>(slot_ref->slot_id()));
- *expr =
VSlotRef::create_shared(cast_set<int>(global_index.value()),
-
cast_set<int>(global_index.value()), -1,
- slot_ref->data_type(),
slot_ref->column_name());
- RETURN_IF_ERROR(expr->get()->prepare(nullptr, RowDescriptor(),
nullptr));
- return Status::OK();
+ return Status::InternalError(
+ "Can not resolve source slot id {} to a table global index
for column {}",
+ slot_ref->slot_id(), slot_ref->column_name());
}
const auto global_index = global_index_it->second;
*expr = VSlotRef::create_shared(cast_set<int>(global_index.value()),
diff --git a/be/src/format_v2/column_mapper.cpp
b/be/src/format_v2/column_mapper.cpp
index 6a0daf903f0..313bbf376f8 100644
--- a/be/src/format_v2/column_mapper.cpp
+++ b/be/src/format_v2/column_mapper.cpp
@@ -21,6 +21,7 @@
#include <cstddef>
#include <memory>
#include <optional>
+#include <set>
#include <sstream>
#include <string_view>
#include <utility>
@@ -1952,6 +1953,7 @@ ColumnMapping*
TableColumnMapper::_find_filter_mapping(GlobalIndex global_index)
Status TableColumnMapper::localize_filters(const std::vector<TableFilter>&
table_filters,
FileScanRequest* file_request,
RuntimeState* runtime_state) {
+ std::set<LocalColumnId> localized_predicate_columns;
FilterProjectionMap filter_projections;
auto filter_mappings = _filter_visible_mappings();
RETURN_IF_ERROR(build_nested_struct_filter_projection_map(table_filters,
filter_mappings,
@@ -2038,7 +2040,37 @@ Status TableColumnMapper::localize_filters(const
std::vector<TableFilter>& table
auto localized_conjunct =
VExprContext::create_shared(std::move(localized_root));
RETURN_IF_ERROR(rewrite_context.prepare_created_exprs(localized_conjunct.get()));
file_request->conjuncts.push_back(std::move(localized_conjunct));
+ for (const auto global_index : table_filter.global_indices) {
+ const auto* mapping = _find_filter_mapping(global_index);
+ if (mapping != nullptr && mapping->file_local_id.has_value() &&
+
filter_conversion_has_local_source(mapping->filter_conversion)) {
+
localized_predicate_columns.emplace(*mapping->file_local_id);
+ }
+ }
+ }
+ }
+
+ // Candidate columns are added before expression rewriting because their
file-block positions
+ // are needed to localize slot refs. If rewriting rejects every filter
that references a visible
+ // column, move its already-merged output/filter projection to the lazy
non-predicate set
+ // instead of forcing it through the eager predicate path.
+ for (auto& mapping : _mappings) {
+ if (!mapping.file_local_id.has_value()) {
+ continue;
+ }
+ const auto local_id = LocalColumnId(*mapping.file_local_id);
+ if (localized_predicate_columns.contains(local_id)) {
+ continue;
+ }
+ const auto predicate_it = std::ranges::find_if(
+ file_request->predicate_columns, [local_id](const
LocalColumnIndex& projection) {
+ return projection.column_id() == local_id;
+ });
+ if (predicate_it == file_request->predicate_columns.end()) {
+ continue;
}
+
file_request->non_predicate_columns.push_back(std::move(*predicate_it));
+ file_request->predicate_columns.erase(predicate_it);
}
return Status::OK();
}
diff --git a/be/src/format_v2/parquet/parquet_statistics.cpp
b/be/src/format_v2/parquet/parquet_statistics.cpp
index b1eba0cf74f..ff72c6982de 100644
--- a/be/src/format_v2/parquet/parquet_statistics.cpp
+++ b/be/src/format_v2/parquet/parquet_statistics.cpp
@@ -39,6 +39,7 @@
#include <utility>
#include <vector>
+#include "common/cast_set.h"
#include "common/config.h"
#include "core/data_type/data_type.h"
#include "core/data_type/data_type_nullable.h"
@@ -962,12 +963,15 @@ Status select_row_groups_by_metadata_impl(
selected_row_groups->clear();
const int num_row_groups = metadata.num_row_groups();
- if (pruning_stats != nullptr) {
- pruning_stats->total_row_groups = num_row_groups;
- }
const auto candidate_size = candidate_row_groups == nullptr
? static_cast<size_t>(num_row_groups)
: candidate_row_groups->size();
+ if (pruning_stats != nullptr) {
+ // Scan-range ownership is decided before metadata pruning. Count only
row groups owned by
+ // this split so a file divided into multiple splits does not report
the full-file total and
+ // out-of-split groups once per split.
+ pruning_stats->total_row_groups = cast_set<int64_t>(candidate_size);
+ }
selected_row_groups->reserve(candidate_size);
RowGroupBloomFilterCache bloom_filter_cache;
init_bloom_filter_cache(file_reader, enable_bloom_filter,
&bloom_filter_cache);
diff --git a/be/src/format_v2/parquet/reader/scalar_column_reader.cpp
b/be/src/format_v2/parquet/reader/scalar_column_reader.cpp
index bdbd9ac8779..520a3cb1e62 100644
--- a/be/src/format_v2/parquet/reader/scalar_column_reader.cpp
+++ b/be/src/format_v2/parquet/reader/scalar_column_reader.cpp
@@ -265,6 +265,14 @@ Status
ScalarColumnReader::select_with_dictionary_filter(const SelectionVector&
*used_filter = false;
row_filter->clear();
row_filter->reserve(selected_rows);
+ DORIS_CHECK(_record_reader != nullptr);
+ // A clean fallback is possible only before any range skip or read
advances the record reader.
+ // Once dictionary selection starts, losing dictionary output is
corruption rather than a
+ // reason to retry through the ordinary selected-read path with an already
advanced stream.
+ if (!_record_reader->read_dictionary()) {
+ return Status::OK();
+ }
+ *used_filter = true;
const auto ranges = selection_to_ranges(sel, selected_rows);
int64_t cursor = 0;
@@ -306,8 +314,10 @@ Status
ScalarColumnReader::read_range_with_dictionary_filter(
DORIS_CHECK(used_filter != nullptr);
DORIS_CHECK(_record_reader != nullptr);
if (!_record_reader->read_dictionary()) {
- *used_filter = false;
- return Status::OK();
+ return Status::Corruption(
+ "Parquet dictionary reader became unavailable after selected
reading started for "
+ "column {}",
+ _name);
}
ParquetLeafBatch leaf_batch;
@@ -365,10 +375,15 @@ Status
ScalarColumnReader::append_dictionary_filtered_values(
bool keep = false;
if (!dict_array->IsNull(row)) {
const int64_t dictionary_index =
dict_array->GetValueIndex(row);
- if (dictionary_index >= 0 &&
- dictionary_index <
static_cast<int64_t>(dictionary_filter.size())) {
- keep =
dictionary_filter[static_cast<size_t>(dictionary_index)] != 0;
+ if (dictionary_index < 0 || dictionary_index >=
dictionary->length() ||
+ dictionary_index >=
static_cast<int64_t>(dictionary_filter.size())) {
+ return Status::Corruption(
+ "Invalid parquet dictionary index {} for column
{}: dictionary={}, "
+ "filter={}",
+ dictionary_index, _name, dictionary->length(),
+ dictionary_filter.size());
}
+ keep =
dictionary_filter[static_cast<size_t>(dictionary_index)] != 0;
if (keep) {
RETURN_IF_ERROR(append_arrow_binary_dictionary_value(
_name, *dictionary, dictionary_index,
&selected_values));
diff --git a/be/src/format_v2/table/iceberg_reader.cpp
b/be/src/format_v2/table/iceberg_reader.cpp
index b817a6d1ead..962a414a4c2 100644
--- a/be/src/format_v2/table/iceberg_reader.cpp
+++ b/be/src/format_v2/table/iceberg_reader.cpp
@@ -832,25 +832,22 @@ Status
IcebergTableReader::_load_equality_delete_file(const TIcebergDeleteFileDe
RETURN_IF_ERROR(_resolve_equality_delete_fields(delete_file, schema,
&delete_fields, result));
auto request = std::make_shared<format::FileScanRequest>();
- auto build_block = [](const std::vector<format::ColumnDefinition>& fields)
-> Block {
- Block block;
- for (const auto& field : fields) {
- block.insert({field.type->create_column(), field.type,
field.name});
- }
- return block;
- };
+ Block delete_block_template;
for (size_t idx = 0; idx < delete_fields.size(); ++idx) {
- const auto local_column_id =
format::LocalColumnId(delete_fields[idx].file_local_id());
+ const auto& delete_field = delete_fields[idx];
+ const auto local_column_id =
format::LocalColumnId(delete_field.file_local_id());
request->non_predicate_columns.push_back(
format::LocalColumnIndex::top_level(local_column_id));
request->local_positions.emplace(local_column_id,
format::LocalIndex(idx));
+ delete_block_template.insert(
+ {delete_field.type->create_column(), delete_field.type,
delete_field.name});
}
RETURN_IF_ERROR(reader->open(request));
- MutableBlock mutable_delete_block(build_block(delete_fields));
+ MutableBlock mutable_delete_block(delete_block_template.clone_empty());
bool eof = false;
while (!eof) {
- Block block = build_block(delete_fields);
+ Block block = delete_block_template.clone_empty();
size_t read_rows = 0;
RETURN_IF_ERROR(reader->get_block(&block, &read_rows, &eof));
if (read_rows > 0) {
diff --git a/be/test/exec/scan/file_scanner_v2_test.cpp
b/be/test/exec/scan/file_scanner_v2_test.cpp
index ea24b4b21eb..34b51a5b40d 100644
--- a/be/test/exec/scan/file_scanner_v2_test.cpp
+++ b/be/test/exec/scan/file_scanner_v2_test.cpp
@@ -772,8 +772,8 @@ TEST(FileScannerV2Test, DataFileSlotClassificationMatrix) {
}
// Scenario: table conjuncts are cloned into global-index space before they
are handed to
-// TableReader. Explicit slot-id mappings use the required_slots order;
missing mappings fall back
-// to the slot id itself for legacy descriptors.
+// TableReader. Explicit slot-id mappings use the required_slots order;
missing mappings are an
+// error because a scanner slot id is not a table-global ordinal.
TEST(FileScannerV2Test, RewriteSlotRefsToGlobalIndexMatrix) {
const auto int_type = std::make_shared<DataTypeInt32>();
{
@@ -789,11 +789,8 @@ TEST(FileScannerV2Test,
RewriteSlotRefsToGlobalIndexMatrix) {
{
auto expr = slot_ref(7, 99, int_type, "legacy_value");
const auto status =
FileScannerV2::TEST_rewrite_slot_refs_to_global_index(&expr, {});
- ASSERT_TRUE(status.ok()) << status;
- const auto* rewritten = assert_cast<const VSlotRef*>(expr.get());
- EXPECT_EQ(rewritten->slot_id(), 7);
- EXPECT_EQ(rewritten->column_id(), 7);
- EXPECT_EQ(rewritten->column_name(), "legacy_value");
+ EXPECT_FALSE(status.ok());
+ EXPECT_NE(status.to_string().find("Can not resolve source slot id 7"),
std::string::npos);
}
{
auto cast_expr = format::Cast::create_shared(int_type);
diff --git a/be/test/format_v2/column_mapper_test.cpp
b/be/test/format_v2/column_mapper_test.cpp
index 3885deb107f..3bca3c4152c 100644
--- a/be/test/format_v2/column_mapper_test.cpp
+++ b/be/test/format_v2/column_mapper_test.cpp
@@ -2648,8 +2648,8 @@ TEST(ColumnMapperScanRequestTest,
StructProjectionPrunesChildrenByName) {
}
// Scenario: a row filter reaches a struct child through an array wrapper
-// (`items.item.a > 5`). The mapper keeps this as a row predicate and reads
the full array root for
-// predicate evaluation.
+// (`items.item.a > 5`). The mapper cannot localize the filter, so it keeps
the full array root in
+// the lazy non-predicate set for table-level evaluation.
TEST(ColumnMapperScanRequestTest,
ArrayWrapperDoesNotBuildNestedPredicateFilter) {
const auto int_type = i32();
const auto string_type = str();
@@ -2674,16 +2674,17 @@ TEST(ColumnMapperScanRequestTest,
ArrayWrapperDoesNotBuildNestedPredicateFilter)
FileScanRequest request;
ASSERT_TRUE(mapper.create_scan_request({filter}, {table_array},
&request).ok());
- EXPECT_TRUE(request.non_predicate_columns.empty());
- ASSERT_EQ(request.predicate_columns.size(), 1);
- EXPECT_EQ(request.predicate_columns[0].column_id(), LocalColumnId(0));
- EXPECT_TRUE(request.predicate_columns[0].project_all_children);
- EXPECT_TRUE(request.predicate_columns[0].children.empty());
+ EXPECT_TRUE(request.conjuncts.empty());
+ EXPECT_TRUE(request.predicate_columns.empty());
+ ASSERT_EQ(request.non_predicate_columns.size(), 1);
+ EXPECT_EQ(request.non_predicate_columns[0].column_id(), LocalColumnId(0));
+ EXPECT_TRUE(request.non_predicate_columns[0].project_all_children);
+ EXPECT_TRUE(request.non_predicate_columns[0].children.empty());
}
// Scenario: a map value struct projects child `b`, while a row filter reads
value child `a`.
-// The filter is too complex to become a file-local nested predicate, but the
predicate projection
-// must replace the output projection for the same map root and contain both
physical value children.
+// The filter is too complex to become a file-local nested predicate. Lazy
demotion must move the
+// merged projection to the non-predicate set without dropping either physical
value child.
TEST(ColumnMapperScanRequestTest,
MapFilterOnlyValueChildMergesWithOutputProjection) {
const auto key_type = i32();
const auto int_type = i32();
@@ -2716,9 +2717,9 @@ TEST(ColumnMapperScanRequestTest,
MapFilterOnlyValueChildMergesWithOutputProject
FileScanRequest request;
ASSERT_TRUE(mapper.create_scan_request({filter}, {table_map},
&request).ok());
- EXPECT_TRUE(request.non_predicate_columns.empty());
- ASSERT_EQ(request.predicate_columns.size(), 1);
- const auto& projection = request.predicate_columns[0];
+ EXPECT_TRUE(request.predicate_columns.empty());
+ ASSERT_EQ(request.non_predicate_columns.size(), 1);
+ const auto& projection = request.non_predicate_columns[0];
EXPECT_EQ(projection.column_id(), LocalColumnId(0));
ASSERT_FALSE(projection.project_all_children);
ASSERT_EQ(projection.children.size(), 1);
@@ -3084,11 +3085,9 @@ TEST(ColumnMapperScanRequestTest,
MapValuesStructChildConjunctStaysTableLevel) {
ASSERT_TRUE(mapper.create_scan_request({filter}, {table_map},
&request).ok());
EXPECT_TRUE(request.conjuncts.empty());
- ASSERT_EQ(request.predicate_columns.size(), 1);
- EXPECT_EQ(request.predicate_columns[0].column_id(), LocalColumnId(1));
- ASSERT_FALSE(request.predicate_columns[0].project_all_children);
- ASSERT_EQ(request.predicate_columns[0].children.size(), 1);
- EXPECT_EQ(request.predicate_columns[0].children[0].local_id(), 1);
+ EXPECT_TRUE(request.predicate_columns.empty());
+ ASSERT_EQ(request.non_predicate_columns.size(), 1);
+ EXPECT_EQ(request.non_predicate_columns[0].column_id(), LocalColumnId(1));
}
// Scenario: MAP_KEYS only reads map keys, but localizing it by wrapping the
evolved file map slot
@@ -3127,9 +3126,9 @@ TEST(ColumnMapperScanRequestTest,
MapKeysConjunctWithEvolvedValueStructStaysTabl
ASSERT_TRUE(mapper.create_scan_request({filter}, {table_map},
&request).ok());
EXPECT_TRUE(request.conjuncts.empty());
- EXPECT_TRUE(request.non_predicate_columns.empty());
- ASSERT_EQ(request.predicate_columns.size(), 1);
- EXPECT_EQ(request.predicate_columns[0].column_id(), LocalColumnId(1));
+ EXPECT_TRUE(request.predicate_columns.empty());
+ ASSERT_EQ(request.non_predicate_columns.size(), 1);
+ EXPECT_EQ(request.non_predicate_columns[0].column_id(), LocalColumnId(1));
}
// Scenario: an array element struct projection only contains missing/default
children; the mapper
diff --git a/be/test/format_v2/parquet/parquet_reader_control_test.cpp
b/be/test/format_v2/parquet/parquet_reader_control_test.cpp
index a21974bced2..2fd85cefd49 100644
--- a/be/test/format_v2/parquet/parquet_reader_control_test.cpp
+++ b/be/test/format_v2/parquet/parquet_reader_control_test.cpp
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+#include <arrow/api.h>
#include <gtest/gtest.h>
#include <algorithm>
@@ -394,10 +395,48 @@ struct ScalarColumnReaderTestAccess {
static void set_row_group_rows_read(ScalarColumnReader* reader, int64_t
rows) {
reader->_row_group_rows_read = rows;
}
+
+ static Status append_dictionary_filtered_values(
+ const ScalarColumnReader& reader,
+ const std::vector<std::shared_ptr<::arrow::Array>>& chunks,
+ const IColumn::Filter& dictionary_filter, MutableColumnPtr& column,
+ IColumn::Filter* row_filter, int64_t* matched_rows, bool*
used_filter) {
+ return reader.append_dictionary_filtered_values(chunks,
dictionary_filter, column,
+ row_filter,
matched_rows, used_filter);
+ }
};
namespace {
+ParquetColumnSchema string_schema(std::string name = "string") {
+ ParquetColumnSchema schema;
+ schema.local_id = 0;
+ schema.name = std::move(name);
+ schema.type = std::make_shared<DataTypeString>();
+ schema.type_descriptor.physical_type = ::parquet::Type::BYTE_ARRAY;
+ schema.type_descriptor.doris_type = schema.type;
+ return schema;
+}
+
+std::shared_ptr<::arrow::Array> dictionary_array(const std::vector<int8_t>&
indices,
+ const
std::vector<std::string>& values) {
+ ::arrow::Int8Builder index_builder;
+ EXPECT_TRUE(index_builder.AppendValues(indices).ok());
+ auto index_result = index_builder.Finish();
+ EXPECT_TRUE(index_result.ok()) << index_result.status();
+
+ ::arrow::StringBuilder dictionary_builder;
+ EXPECT_TRUE(dictionary_builder.AppendValues(values).ok());
+ auto dictionary_result = dictionary_builder.Finish();
+ EXPECT_TRUE(dictionary_result.ok()) << dictionary_result.status();
+
+ auto result = ::arrow::DictionaryArray::FromArrays(
+ ::arrow::dictionary(::arrow::int8(), ::arrow::utf8()),
*index_result,
+ *dictionary_result);
+ EXPECT_TRUE(result.ok()) << result.status();
+ return *result;
+}
+
std::unique_ptr<ScalarColumnReader> make_scripted_scalar_reader(
ParquetColumnSchema schema, std::unique_ptr<ParquetNestedScalarBatch>
batch) {
auto reader = std::make_unique<ScalarColumnReader>(schema, nullptr);
@@ -441,6 +480,21 @@ GlobalRowLoacationV2 decode_rowid(const ColumnString&
column, size_t row) {
return location;
}
+TEST(ParquetScalarColumnReaderTest, DictionaryIndexOutsideFilterIsCorruption) {
+ ScalarColumnReader reader(string_schema("dictionary_value"), nullptr);
+ MutableColumnPtr column = ColumnString::create();
+ IColumn::Filter row_filter;
+ int64_t matched_rows = 0;
+ bool used_filter = false;
+ const std::vector<std::shared_ptr<::arrow::Array>> chunks = {
+ dictionary_array({0, 1}, {"keep", "out-of-range"})};
+
+ const auto status =
ScalarColumnReaderTestAccess::append_dictionary_filtered_values(
+ reader, chunks, IColumn::Filter {1}, column, &row_filter,
&matched_rows, &used_filter);
+ EXPECT_EQ(ErrorCode::CORRUPTION, status.code()) << status;
+ EXPECT_NE(status.to_string().find("Invalid parquet dictionary index 1"),
std::string::npos);
+}
+
} // namespace
TEST(SelectionVectorTest, IdentitySelectionToRanges) {
diff --git a/be/test/format_v2/parquet/parquet_reader_test.cpp
b/be/test/format_v2/parquet/parquet_reader_test.cpp
index c5bf251c155..7402595fc37 100644
--- a/be/test/format_v2/parquet/parquet_reader_test.cpp
+++ b/be/test/format_v2/parquet/parquet_reader_test.cpp
@@ -2374,7 +2374,7 @@ TEST_F(NewParquetReaderTest,
ScanRangeFiltersRowGroupsBeforeDictionaryPruning) {
.ok());
ASSERT_EQ(plan.row_groups.size(), 1);
EXPECT_EQ(plan.row_groups[0].row_group_id, 2);
- EXPECT_EQ(plan.pruning_stats.total_row_groups, 6);
+ EXPECT_EQ(plan.pruning_stats.total_row_groups, 1);
EXPECT_EQ(plan.pruning_stats.selected_row_groups, 1);
EXPECT_EQ(plan.pruning_stats.filtered_row_groups_by_dictionary, 0);
EXPECT_EQ(plan.pruning_stats.filtered_group_rows, 0);
diff --git a/be/test/format_v2/parquet/parquet_scan_test.cpp
b/be/test/format_v2/parquet/parquet_scan_test.cpp
index ec02b8700d1..55387d2c1ed 100644
--- a/be/test/format_v2/parquet/parquet_scan_test.cpp
+++ b/be/test/format_v2/parquet/parquet_scan_test.cpp
@@ -515,7 +515,7 @@ TEST_F(ParquetScanTest,
PlanRowGroupsAppliesScanRangeBeforeStatistics) {
request, scan_range,
false, &plan)
.ok());
EXPECT_TRUE(plan.row_groups.empty());
- EXPECT_EQ(plan.pruning_stats.total_row_groups, 3);
+ EXPECT_EQ(plan.pruning_stats.total_row_groups, 1);
EXPECT_EQ(plan.pruning_stats.selected_row_groups, 0);
EXPECT_EQ(plan.pruning_stats.filtered_row_groups_by_statistics, 1);
EXPECT_EQ(plan.pruning_stats.filtered_group_rows, 2);
diff --git a/be/test/format_v2/table_reader_test.cpp
b/be/test/format_v2/table_reader_test.cpp
index b5ec1dfc765..05ef805ae71 100644
--- a/be/test/format_v2/table_reader_test.cpp
+++ b/be/test/format_v2/table_reader_test.cpp
@@ -3680,14 +3680,13 @@ TEST(TableReaderTest,
CreateScanRequestDeduplicatesSharedPredicateColumns) {
std::vector<TableFilter> table_filters;
table_filters.push_back({
- // This test only needs the referenced global indices to drive
predicate-column
- // placement. Keep the conjunct empty so the assertion focuses on
scan-column
- // de-duplication rather than expression rewrite/prepare behavior.
- .conjunct = nullptr,
+ .conjunct =
+
VExprContext::create_shared(table_int32_sum_greater_than_expr(0, 0, 1, 1, 1)),
.global_indices = {GlobalIndex(0), GlobalIndex(1)},
});
table_filters.push_back({
- .conjunct = nullptr,
+ .conjunct =
+
VExprContext::create_shared(table_int32_sum_greater_than_expr(0, 0, 2, 2, 1)),
.global_indices = {GlobalIndex(0), GlobalIndex(2)},
});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]