github-actions[bot] commented on code in PR #66412:
URL: https://github.com/apache/doris/pull/66412#discussion_r3709424578


##########
be/src/format_v2/parquet/parquet_scan.cpp:
##########
@@ -2421,6 +2759,121 @@ Status 
ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
         return status;
     };
 
+    auto execute_raw_disjunction = [&](const detail::PredicateConjunctStage& 
stage,
+                                       bool* applied) -> Status {
+        DORIS_CHECK(applied != nullptr);
+        *applied = false;
+        if (stage.raw_disjunction_branches.empty() || *selected_rows == 0) {
+            return Status::OK();
+        }
+        auto readers_it = 
_current_raw_disjunction_readers.find(stage.expression.get());
+        if (readers_it == _current_raw_disjunction_readers.end()) {
+            return Status::OK();
+        }
+
+        const uint16_t selected_rows_before = *selected_rows;
+        // PODArray resize preserves retained bytes, but every OR group must 
start from FALSE or a
+        // survivor bit from the previous batch could admit a row that matches 
no current branch.
+        _raw_disjunction_filter_scratch.resize(selected_rows_before);
+        std::ranges::fill(_raw_disjunction_filter_scratch, 0);
+        size_t dictionary_branches = 0;
+        size_t raw_value_branches = 0;
+        size_t fixed_width_branches = 0;
+        for (auto& branch : readers_it->second) {
+            ParquetColumnReader* branch_reader = branch.reader.get();
+            if (branch_reader == nullptr) {
+                const auto predicate_reader_it = 
_current_predicate_columns.find(branch.local_id);
+                DORIS_CHECK(predicate_reader_it != 
_current_predicate_columns.end());
+                branch_reader = predicate_reader_it->second.get();
+            }
+            bool used_filter = false;
+            if (branch.dictionary_filter.has_value()) {
+                uint16_t branch_survivors = 0;
+                RETURN_IF_ERROR(branch_reader->select_with_dictionary_filter(
+                        *selection, selected_rows_before, batch_rows, 
*branch.dictionary_filter,
+                        nullptr, &_raw_disjunction_branch_filter_scratch, 
&branch_survivors,
+                        &used_filter));
+                if (used_filter) {
+                    ++dictionary_branches;
+                }
+            } else {
+                DirectPredicateExecutionKind execution_kind = 
DirectPredicateExecutionKind::NONE;
+                const VExprSPtrs branch_conjuncts {branch.expression};
+                RETURN_IF_ERROR(branch_reader->select_with_fixed_width_filter(
+                        *selection, selected_rows_before, batch_rows, 
branch_conjuncts,
+                        cast_set<int>(branch.position), nullptr,
+                        &_raw_disjunction_branch_filter_scratch, &used_filter, 
&execution_kind));
+                if (used_filter &&
+                    (execution_kind == DirectPredicateExecutionKind::RAW_FIXED 
||
+                     execution_kind == 
DirectPredicateExecutionKind::RAW_BINARY ||
+                     execution_kind == 
DirectPredicateExecutionKind::CONVERTED_FIXED)) {
+                    ++raw_value_branches;
+                }
+                if (used_filter &&
+                    (execution_kind == DirectPredicateExecutionKind::RAW_FIXED 
||
+                     execution_kind == 
DirectPredicateExecutionKind::CONVERTED_FIXED)) {
+                    ++fixed_width_branches;
+                }
+            }
+            if (!used_filter) {
+                if (!branch.reader) {
+                    return Status::InternalError(
+                            "Validated multi-column OR branch {} could not 
execute directly",
+                            branch.position);
+                }
+                // No ordinary predicate reader has advanced yet. Dropping the 
independent raw

Review Comment:
   [P1] Do not fall back after an ordinary branch has advanced
   
   The assertion in this comment is not guaranteed: branches run in expression 
order, and an earlier hidden single-use branch may already have consumed its 
ordinary predicate reader. If a later auxiliary branch then returns 
`used_filter=false` (for example, a >=50%-NULL mixed dictionary/PLAIN chunk), 
this erases the auxiliaries and resumes residual evaluation. 
`materialize_predicate_positions()` then reads the earlier ordinary reader 
again for the same batch, so that branch supplies the next span's values and 
can silently select the wrong source rows. The stage needs a non-consuming 
all-branch capability check, or all branches must execute on independent 
readers until success can be committed.



##########
be/benchmark/parquet/benchmark_parquet_reader.hpp:
##########
@@ -234,7 +253,7 @@ inline std::filesystem::path ensure_fixture(const 
ReaderScenario& scenario) {
     properties.version(::parquet::ParquetVersion::PARQUET_2_6);
     properties.data_page_version(::parquet::ParquetDataPageVersion::V2);
     properties.compression(::parquet::Compression::UNCOMPRESSED);
-    properties.disable_statistics();
+    // Keep footer statistics enabled to match the common production fixture 
shape.

Review Comment:
   [P1] Keep statistics out of the decode benchmark fixtures
   
   This changes the entire existing reader matrix, not just the new OR cases 
that need footer `null_count`. For example, fixture values are `row % 100` and 
`sel_0` is `c < 0`; with statistics enabled, ZoneMap planning proves every Row 
Group has no match during open, so those timed cases perform no row-level 
decode, predicate compaction, or lazy materialization. That invalidates their 
role as decode/filter benchmarks (and the benchmark guide still says statistics 
are disabled and pruning cases must be separate). Please keep the existing 
fixtures statistics-free and introduce a distinct statistics-enabled 
fixture/mode for the OR policy or explicit pruning benchmarks.



##########
be/src/format_v2/parquet/parquet_scan.cpp:
##########
@@ -2421,6 +2759,121 @@ Status 
ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
         return status;
     };
 
+    auto execute_raw_disjunction = [&](const detail::PredicateConjunctStage& 
stage,
+                                       bool* applied) -> Status {
+        DORIS_CHECK(applied != nullptr);
+        *applied = false;
+        if (stage.raw_disjunction_branches.empty() || *selected_rows == 0) {
+            return Status::OK();
+        }
+        auto readers_it = 
_current_raw_disjunction_readers.find(stage.expression.get());
+        if (readers_it == _current_raw_disjunction_readers.end()) {
+            return Status::OK();
+        }
+
+        const uint16_t selected_rows_before = *selected_rows;
+        // PODArray resize preserves retained bytes, but every OR group must 
start from FALSE or a
+        // survivor bit from the previous batch could admit a row that matches 
no current branch.
+        _raw_disjunction_filter_scratch.resize(selected_rows_before);
+        std::ranges::fill(_raw_disjunction_filter_scratch, 0);
+        size_t dictionary_branches = 0;
+        size_t raw_value_branches = 0;
+        size_t fixed_width_branches = 0;
+        for (auto& branch : readers_it->second) {
+            ParquetColumnReader* branch_reader = branch.reader.get();
+            if (branch_reader == nullptr) {
+                const auto predicate_reader_it = 
_current_predicate_columns.find(branch.local_id);
+                DORIS_CHECK(predicate_reader_it != 
_current_predicate_columns.end());
+                branch_reader = predicate_reader_it->second.get();
+            }
+            bool used_filter = false;
+            if (branch.dictionary_filter.has_value()) {
+                uint16_t branch_survivors = 0;
+                RETURN_IF_ERROR(branch_reader->select_with_dictionary_filter(
+                        *selection, selected_rows_before, batch_rows, 
*branch.dictionary_filter,
+                        nullptr, &_raw_disjunction_branch_filter_scratch, 
&branch_survivors,
+                        &used_filter));
+                if (used_filter) {
+                    ++dictionary_branches;
+                }
+            } else {
+                DirectPredicateExecutionKind execution_kind = 
DirectPredicateExecutionKind::NONE;
+                const VExprSPtrs branch_conjuncts {branch.expression};
+                RETURN_IF_ERROR(branch_reader->select_with_fixed_width_filter(
+                        *selection, selected_rows_before, batch_rows, 
branch_conjuncts,
+                        cast_set<int>(branch.position), nullptr,
+                        &_raw_disjunction_branch_filter_scratch, &used_filter, 
&execution_kind));
+                if (used_filter &&
+                    (execution_kind == DirectPredicateExecutionKind::RAW_FIXED 
||
+                     execution_kind == 
DirectPredicateExecutionKind::RAW_BINARY ||
+                     execution_kind == 
DirectPredicateExecutionKind::CONVERTED_FIXED)) {
+                    ++raw_value_branches;
+                }
+                if (used_filter &&
+                    (execution_kind == DirectPredicateExecutionKind::RAW_FIXED 
||
+                     execution_kind == 
DirectPredicateExecutionKind::CONVERTED_FIXED)) {
+                    ++fixed_width_branches;
+                }
+            }
+            if (!used_filter) {
+                if (!branch.reader) {
+                    return Status::InternalError(

Review Comment:
   [P1] Fall back before reusing an unsupported ordinary reader
   
   A valid mixed dictionary/PLAIN chunk can reach this branch. 
`is_fully_dictionary_encoded_chunk()` classifies it as non-dictionary, a hidden 
column with at least 50% NULLs passes the raw gate, but 
`ScalarColumnReader::read_fixed_width_filter()` rejects the advertised 
`RLE_DICTIONARY` encoding and returns `used_filter=false` before consuming. 
Because a hidden single-use branch stores `reader == nullptr`, enabling this 
optimization turns a supported residual scan into this `InternalError`. Please 
include the native reader's complete per-Row-Group encoding capability in the 
all-branch preflight (or otherwise keep all ordinary cursors untouched) so an 
unsupported chunk retains the residual path.



##########
be/benchmark/parquet/benchmark_parquet_reader.hpp:
##########
@@ -493,6 +560,29 @@ inline std::unique_ptr<ReaderSession> open_reader(const 
std::filesystem::path& p
         throw_if_error(context->open(&session->runtime_state));
         session->request->conjuncts.push_back(context);
         session->opened_conjuncts.push_back(std::move(context));
+    } else if (scenario.operation == ReaderOperation::MULTI_COLUMN_OR_SCAN) {
+        DORIS_CHECK(scenario.value_type == ValueType::DECIMAL64);
+        DORIS_CHECK(scenario.schema_width >= 4);
+        std::array<int, 3> predicate_positions {};
+        for (int column = 0; column < 3; ++column) {
+            const auto predicate_id = format::LocalColumnId(column);
+            throw_if_error(request_builder.add_predicate_column(predicate_id));
+            if (scenario.projection == Projection::PREDICATE_ONLY || column != 
0) {
+                
session->request->predicate_only_columns.push_back(predicate_id);
+            }
+            predicate_positions[column] =
+                    
static_cast<int>(session->request->local_positions.at(predicate_id).value());
+        }
+        throw_if_error(request_builder.add_non_predicate_column(
+                format::LocalColumnId(scenario.schema_width - 1)));
+        auto context = make_multi_column_or_predicate(scenario, 
predicate_positions,
+                                                      session->schema[0].type);
+        throw_if_error(context->prepare(&session->runtime_state, 
RowDescriptor()));
+        throw_if_error(context->open(&session->runtime_state));
+        session->request->conjuncts.push_back(context);
+        session->request->enable_multi_column_or_raw_filter =

Review Comment:
   [P1] Make the raw benchmark execute and verify the raw path
   
   This flag only requests the production policy; it does not force the 
implementation named by the scenario. Under the checked-in gates, 65 of the 100 
`impl_raw_disjunction` cases (including every projected PLAIN case) fall back, 
so those registrations measure the residual path on both sides. The PR body 
acknowledges the 35/65 split, but the regressions from the earlier unrestricted 
implementation are what justify the hard-coded 10%/50% cutoffs, and this 
benchmark has neither a force-raw mode nor a profile/path assertion to 
reproduce them. Please add a benchmark-only capability-checked force mode and 
verify the selected path before timing, while retaining the current 
policy-fallback cases as separately named controls.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to