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


##########
be/src/format_v2/parquet/parquet_scan.cpp:
##########
@@ -1866,39 +1960,103 @@ Status 
ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
             if (*selected_rows != 0) {
                 continue;
             }
-            for (size_t remaining_order_idx = order_idx + 1;
-                 remaining_order_idx < ordered_positions.size(); 
++remaining_order_idx) {
-                const size_t remaining_idx = 
_predicate_indices_by_position_scratch.at(
-                        ordered_positions[remaining_order_idx]);
-                const auto remaining_fid = 
request.predicate_columns[remaining_idx].column_id();
-                auto remaining_reader_it = 
_current_predicate_columns.find(remaining_fid);
-                DORIS_CHECK(remaining_reader_it != 
_current_predicate_columns.end());
-                RETURN_IF_ERROR(remaining_reader_it->second->skip(batch_rows));
-            }
             return Status::OK();
         }
         return Status::OK();
     };
 
+    auto materialize_predicate_positions = [&](const std::vector<size_t>& 
positions) -> Status {
+        for (const size_t position : positions) {
+            if (materialized_positions.contains(position)) {
+                continue;
+            }
+            const auto index_it = 
_predicate_indices_by_position_scratch.find(position);
+            DORIS_CHECK(index_it != 
_predicate_indices_by_position_scratch.end());
+            const auto fid = 
request.predicate_columns[index_it->second].column_id();
+            const auto reader_it = _current_predicate_columns.find(fid);
+            DORIS_CHECK(reader_it != _current_predicate_columns.end());
+            bool used_dictionary_filter = false;
+            bool used_fixed_width_filter = false;
+            RETURN_IF_ERROR(read_predicate_column(reader_it->second.get(), 
position, fid, nullptr,
+                                                  &used_dictionary_filter,
+                                                  &used_fixed_width_filter));
+            materialized_positions.insert(position);
+        }
+        return Status::OK();
+    };
+
+    auto skip_unmaterialized_predicate_columns = [&]() -> Status {
+        for (const auto& col : request.predicate_columns) {
+            const auto position_it = 
request.local_positions.find(col.column_id());
+            DORIS_CHECK(position_it != request.local_positions.end());
+            if (materialized_positions.contains(position_it->second.value())) {
+                continue;
+            }
+            const auto reader_it = 
_current_predicate_columns.find(col.column_id());
+            DORIS_CHECK(reader_it != _current_predicate_columns.end());
+            RETURN_IF_ERROR(reader_it->second->skip(batch_rows));
+        }
+        // Every skipped column has an empty payload in the block. Suppress 
the caller's
+        // batch-coordinate filter because there is no materialized 
batch-sized column left.
+        *predicate_columns_filtered = true;
+        return Status::OK();
+    };
+
     auto compact_predicate_columns_with_profile =
             [&](bool discard_predicate_only_payload) -> Status {
+        if (!discard_predicate_only_payload && 
!predicate_columns_need_alignment) {
+            return Status::OK();
+        }
         const int64_t start_ns = MonotonicNanos();
         auto status = 
compact_predicate_columns(discard_predicate_only_payload);
         update_counter_if_not_null(_scan_profile.predicate_compaction_time,
                                    MonotonicNanos() - start_ns);
+        if (status.ok()) {
+            predicate_columns_need_alignment = false;
+        }
         return status;
     };
 
     RETURN_IF_ERROR(read_round_by_round());
-    // Single-column expressions only touch the just-read column, so earlier 
columns can retain
-    // their own row mappings. Compact only when a later expression needs a 
shared coordinate
-    // space; otherwise the final boundary can discard hidden predicate 
payloads without scanning
-    // them again.
-    if (!schedule.remaining_conjuncts.empty()) {
+    if (*selected_rows == 0) {
+        RETURN_IF_ERROR(skip_unmaterialized_predicate_columns());
+        return compact_predicate_columns_with_profile(true);
+    }
+
+    // Complex residuals keep their original conjunct order. Materialize only 
the columns needed
+    // by the next reachable expression, then compact previously read columns 
into the same row
+    // space before evaluating it. This is the scanner-side equivalent of 
expression-triggered
+    // lazy columns: a conjunct that rejects the batch prevents later-only 
columns from decoding.
+    for (const auto& stage : schedule.remaining_stages) {
+        
RETURN_IF_ERROR(materialize_predicate_positions(stage.required_positions));

Review Comment:
   [P1] Preserve decode failures for rows skipped by later stages
   
   This safety check covers the `VExpr` tree, but the later column is also 
subject to physical-to-logical conversion while it is materialized. After an 
earlier AND child rejects a row, `materialize_predicate_positions()` calls the 
native reader with that row filtered out; `decode_selected_values()` handles 
`FILTERED_CONTENT` with `skip_values()`, and the sparse path runs the SerDe 
only for selected ranges. For example, with `enable_strict_cast=true`, an 
out-of-range INT96 value in a row rejected by stage 1 used to make eager 
materialization return `DataQualityError`, while the staged path skips that 
payload and can return rows successfully. This is below the `format::Cast` 
safeguard from the existing thread. Please either limit staged materialization 
to file-column conversions that are total for every physical value, or validate 
filtered payloads equivalently to eager decoding, and cover a strict-cast 
partial-survival invalid-INT96 case.



##########
be/benchmark/parquet/benchmark_parquet_reader.hpp:
##########
@@ -284,10 +339,17 @@ inline Block make_block(const 
std::vector<format::ColumnDefinition>& schema) {
 }
 
 struct ReaderSession {
+    ~ReaderSession() {

Review Comment:
   [P2] Destroy the expression session while timing is paused
   
   `run_reader()` resumes timing immediately after `reader->close()`, but the 
local `session` is destroyed only at the end of the loop body. For this new 
case, that destructor calls `context->close()` in the measured region. The 
reader still retains the request that owns the same prepared context, and 
`VExprContext::~VExprContext()` closes it again because explicit `close()` does 
not clear `_opened`; request/reader destruction is timed as well. That makes 
the complex-residual number include duplicate expression teardown even though 
the benchmark contract excludes setup/close work. Please reset/destroy the 
session while timing is paused and use a single close ownership path.



-- 
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