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]

Reply via email to