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


##########
be/src/format_v2/table_reader.h:
##########
@@ -900,6 +919,14 @@ class TableReader {
         if (agg_type != TPushAggOp::type::COUNT && agg_type != 
TPushAggOp::type::MINMAX) {
             return false;
         }
+        // Aggregate pushdown returns reduced synthetic rows and may close the 
physical reader
+        // before the next scheduler turn. If a runtime filter is still 
pending, those rows could
+        // escape before the filter arrives and cannot later be reconstructed 
from real file rows.
+        // This is the same irreversibility constraint as table-level metadata 
COUNT, and applies
+        // to COUNT and MIN/MAX for Parquet/ORC as well as COUNT for text 
readers.
+        if (!_all_runtime_filters_applied_for_split) {

Review Comment:
   This still allows aggregate pushdown to reduce rows before an original 
row-level predicate has run. `_build_table_filters_from_conjuncts()` 
intentionally omits slotless conjuncts, so a scan with `MIN/MAX(v)` and a 
slotless unsafe predicate like `random()`/`uuid()` can reach this new RF gate 
with no pending filters and then pass the later `_table_filters.empty()` check. 
The reader then emits synthetic extrema rows and closes before 
`Scanner::_filter_output_block()` evaluates the original predicate, so the 
predicate runs on `{min,max}` instead of on every source row. For rows 
`{1,2,3}`, if the slotless predicate keeps only the middle row, normal scanning 
aggregates `2`, but pushdown can aggregate `{1,3}`. Please disable aggregate 
pushdown whenever the original `_conjuncts` is non-empty, or otherwise prove 
every original conjunct has been represented by a safe, row-preserving 
pre-execution path before emitting synthetic COUNT/MIN/MAX rows.



##########
be/src/format_v2/column_mapper.cpp:
##########
@@ -540,6 +606,16 @@ static VExprSPtr rewrite_literal_to_file_type(const 
VExprSPtr& literal_expr,
     if (rewrite_info.file_type->equals(*original_literal->data_type())) {
         return original_literal;
     }
+    // A literal round trip alone cannot prove that file-local evaluation is 
safe: the file slot
+    // itself may lose information when materialized as the table type. For 
example, DOUBLE 1.5
+    // becomes BIGINT 1, so table predicate `value = 1` is true while file 
predicate
+    // `value = 1.0` is false. Complex Field equality also does not compare 
nested contents.
+    // Restrict localization to scalar numeric casts that preserve every file 
value; unsupported
+    // and complex casts keep the table predicate and evaluate after 
materialization.
+    if (!is_lossless_file_to_table_numeric_cast(rewrite_info.file_type,

Review Comment:
   This numeric-only guard leaves TIMESTAMPTZ scale-mismatch predicates to the 
generic slot rewrite path, which then wraps the file slot in `Cast(file_slot AS 
table_type)`. That conflicts with the mapping contract added for TIMESTAMPTZ 
scale mismatch: finalization treats TIMESTAMPTZ(6) -> TIMESTAMPTZ(0) as 
pass-through because the SQL cast rounds fractional seconds. For example, a 
file value `12:34:56.789` and predicate `ts < '12:34:57'` should remain true 
under pass-through semantics, but a pushed file-reader cast to TIMESTAMPTZ(0) 
can round the value to `12:34:57` and filter it before the scanner evaluates 
the original predicate. Please keep these scale-mismatch TIMESTAMPTZ filters 
above the file reader, or add a no-rounding file-local rewrite that matches 
final materialization semantics.



##########
be/src/format_v2/column_mapper.cpp:
##########
@@ -990,6 +1078,18 @@ static bool mapping_can_use_file_column_directly(const 
ColumnMapping& mapping) {
     return !needs_complex_rematerialize(mapping);
 }
 
+static FilterConversionType direct_filter_conversion(const ColumnMapping& 
mapping) {
+    DORIS_CHECK(mapping.table_type != nullptr);
+    // FileScanOperator deliberately keeps VARBINARY predicates above external 
readers. Their
+    // physical binary representations are not uniformly supported by 
reader-side expression and
+    // metadata filtering, so localizing a late runtime filter here can 
incorrectly reject rows.
+    if (remove_nullable(mapping.table_type)->get_primitive_type() == 
TYPE_VARBINARY) {

Review Comment:
   This guard only protects top-level VARBINARY mappings. Predicates rooted at 
a complex column are admitted by `localize_filters()` based on the root 
mapping, so nested VARBINARY can still reach the file reader. For example, 
`element_at(s, 'bin') = ...` rewrites the struct selector/type to the file 
child without checking the child mapping's `filter_conversion`; similarly, 
generic complex-root predicates such as `array_contains(array<varbinary>, ...)` 
or `map_contains_key(map<varbinary,...>, ...)` rewrite the root slot directly 
when the root mapping has a local source. That sends nested VARBINARY 
comparisons into the same file-reader expression/metadata paths this guard is 
trying to avoid for top-level VARBINARY. Please reject localization for any 
complex-root predicate whose referenced nested mapping is `FINALIZE_ONLY`, or 
at least whose nested table type contains VARBINARY.



##########
be/src/format_v2/orc/orc_reader.cpp:
##########
@@ -524,13 +525,25 @@ bool set_timestamp_zone_map(const 
::orc::ColumnStatistics& statistics,
         !timestamp_statistics->hasMaximum()) {
         return false;
     }
+    const auto min_endpoint =
+            std::pair(timestamp_statistics->getMinimum(), 
timestamp_statistics->getMinimumNanos());
+    const auto max_endpoint =
+            std::pair(timestamp_statistics->getMaximum(), 
timestamp_statistics->getMaximumNanos());
+    if (min_endpoint > max_endpoint) {

Review Comment:
   This adds conservative validation for timestamp stats only, but ORC MIN/MAX 
pushdown also consumes the sibling non-timestamp stripe stats through 
`build_zone_map_from_orc_statistics()`. The integer, floating, string, date, 
and decimal setters still return usable ZoneMaps without checking `max_value < 
min_value`, and the floating path also trusts NaN bounds. With corrupt ORC 
stats such as INT min=10/max=1 for a stripe that actually contains 5, aggregate 
pushdown can return synthetic extrema `{10,1}` instead of falling back to a 
real scan. Please apply the same conservative validation to every ORC 
statistics-backed min/max path, including rejecting NaN floating bounds and 
decoded inverted ranges, before allowing pruning or aggregate pushdown to trust 
the stats.



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