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


##########
be/src/exprs/vin_predicate.cpp:
##########
@@ -218,7 +218,7 @@ ZoneMapFilterResult 
VInPredicate::evaluate_bloom_filter(const BloomFilterEvalCon
 
 bool VInPredicate::can_evaluate_bloom_filter() const {
     return _zonemap_materialized && !_is_not_in &&

Review Comment:
   [P1] Materialize nested IN probes before checking Bloom capability
   
   This new accessor-aware check still requires `_zonemap_materialized`, but 
`_materialize_for_zonemap_filter()` returns early unless child 0 is a direct 
slot. A production `element_at(s, 'a') IN (2, 4)` therefore always reports 
false here, so the nested-IN part of this feature is unreachable; the test's 
`BloomInExpr` masks this by hard-coding capability and values. Please allow 
valid primitive nested probes to materialize their constant set while retaining 
the direct-slot gates for ZoneMap/dictionary/raw paths, and add a prepared 
production `VInPredicate` test.



##########
be/src/format_v2/parquet/parquet_statistics.cpp:
##########
@@ -814,30 +889,34 @@ ParquetRowGroupPruneReason 
native_bloom_filter_prune_reason(
             continue;
         }
         const auto* column_schema = resolve_local_leaf_schema(file_schema, 
*file_column_id);
-        if (column_schema == nullptr || column_schema->type == nullptr ||
-            !native_metadata_predicate_is_type_safe(*column_schema) ||
-            !bloom_filter_supported(*column_schema) ||
-            column_schema->leaf_column_id >= 
static_cast<int>(row_group.columns.size())) {
+        if (column_schema == nullptr) {
             continue;
         }
-        const auto& chunk = row_group.columns[column_schema->leaf_column_id];
-        if (!chunk.__isset.meta_data) {
+        if (bloom_filter_excludes(*column_schema, slot_index, conjuncts)) {
+            return ParquetRowGroupPruneReason::BLOOM_FILTER;
+        }
+    }
+
+    for (const auto& conjunct : request.conjuncts) {
+        if (conjunct == nullptr || conjunct->root() == nullptr ||
+            !conjunct->root()->can_evaluate_bloom_filter()) {
             continue;
         }
-        std::unique_ptr<native::BlockSplitBloomFilter> bloom_filter;
-        Status status;
-        {
-            int64_t timer_sink = 0;
-            SCOPED_RAW_TIMER(pruning_stats == nullptr ? &timer_sink
-                                                      : 
&pruning_stats->bloom_filter_read_time);
-            status = read_native_bloom_filter(chunk.meta_data, 
file_context->native_file,
-                                              file_context->native_io_ctx, 
&bloom_filter);
+        auto probe = 
expr_zonemap::extract_bloom_filter_predicate_probe(conjunct->root());
+        if (!probe.has_value() || probe->path.empty()) {
+            continue;
+        }
+        const auto file_column_id = file_column_id_by_block_position(request, 
probe->slot_index);
+        if (!file_column_id.has_value()) {
+            continue;
         }
-        if (!status.ok() || bloom_filter == nullptr) {
+        const auto* column_schema =
+                resolve_bloom_filter_leaf_schema(file_schema, *file_column_id, 
*probe);
+        if (column_schema == nullptr ||
+            !expr_zonemap::data_types_compatible(column_schema->type, 
probe->value_type)) {
             continue;
         }
-        if (ParquetStatisticsUtils::NativeBloomFilterExcludes(*column_schema, 
slot_index, conjuncts,
-                                                              *bloom_filter)) {
+        if (bloom_filter_excludes(*column_schema, probe->slot_index, 
{conjunct})) {

Review Comment:
   [P2] Share one Bloom read across predicates on the same nested leaf
   
   This loop calls `bloom_filter_excludes` once per conjunct, and each call 
reads the header and payload and reparses the same physical Bloom. Two 
independently retained Bloom-capable predicates, such as equality and non-null 
null-safe equality on the same nested leaf, therefore duplicate remote I/O for 
every surviving Row Group, whereas the top-level path groups predicates and 
reads once. Please group by the resolved physical leaf (or cache the decoded 
Bloom per leaf) and add a counting-reader test that asserts one header/payload 
pair.



##########
be/src/exprs/expr_zonemap_filter.cpp:
##########
@@ -213,6 +246,113 @@ std::optional<SlotLiteral> extract_slot_and_literal(const 
VExprSPtrs& args) {
     return std::nullopt;
 }
 
+std::optional<BloomFilterProbe> extract_bloom_filter_probe(const VExprSPtr& 
expr) {
+    if (expr == nullptr || expr->data_type() == nullptr) {
+        return std::nullopt;
+    }
+    if (auto slot = std::dynamic_pointer_cast<VSlotRef>(expr); slot) {
+        return BloomFilterProbe {
+                .slot_index = slot->column_id(), .value_type = 
slot->data_type(), .path = {}};
+    }
+    if ((expr->fn().name.function_name != "element_at" &&
+         expr->fn().name.function_name != "struct_element") ||
+        expr->get_num_children() != 2) {
+        return std::nullopt;
+    }
+
+    auto probe = extract_bloom_filter_probe(expr->get_child(0));
+    auto selector = field_from_literal_expr(expr->get_child(1));
+    if (!probe.has_value() || !selector.has_value() || 
selector->first.is_null()) {
+        return std::nullopt;
+    }
+    const auto parent_type = remove_nullable(expr->get_child(0)->data_type());
+    if (parent_type == nullptr) {
+        return std::nullopt;
+    }
+
+    BloomFilterPathElement path_element;
+    switch (parent_type->get_primitive_type()) {
+    case TYPE_STRUCT: {
+        path_element.kind = BloomFilterPathKind::STRUCT_FIELD;
+        const auto selector_type = remove_nullable(selector->second);
+        if (selector_type == nullptr) {
+            return std::nullopt;
+        }
+        if (is_string_type(selector_type->get_primitive_type())) {
+            path_element.field_name = selector->first.get<TYPE_STRING>();
+        } else {
+            auto ordinal = struct_field_ordinal(selector->first);
+            if (!ordinal.has_value()) {
+                return std::nullopt;
+            }
+            path_element.field_ordinal = *ordinal;
+        }
+        break;
+    }
+    case TYPE_ARRAY:
+        // Array element positions share one repeated Parquet leaf; membership 
in that leaf is a
+        // necessary condition for any element_at(array, constant) equality to 
match.
+        path_element.kind = BloomFilterPathKind::LIST_ELEMENT;
+        break;
+    default:
+        return std::nullopt;
+    }
+    probe->value_type = expr->data_type();
+    probe->path.push_back(std::move(path_element));
+    return probe;
+}
+
+std::optional<BloomFilterProbe> extract_bloom_filter_predicate_probe(const 
VExprSPtr& expr) {
+    if (auto probe = extract_bloom_filter_probe(expr); probe.has_value()) {
+        return probe;
+    }
+    if (expr == nullptr) {
+        return std::nullopt;
+    }
+    std::optional<BloomFilterProbe> result;
+    for (uint16_t child_idx = 0; child_idx < expr->get_num_children(); 
++child_idx) {
+        const auto& child = expr->get_child(child_idx);
+        if (child == nullptr || child->is_literal()) {
+            continue;
+        }
+        auto child_probe = extract_bloom_filter_predicate_probe(child);
+        if (!child_probe.has_value()) {

Review Comment:
   [P1] Reject compound trees without one unique Bloom probe
   
   Here `nullopt` means either that a child has no probe or that it contains 
conflicting nested probes, and the parent silently ignores both cases. For 
`((s.a = 1 AND s.b = 2) OR s.a = 3)`, the inner AND is skipped, this returns 
`s.a`, and `VCompoundPred` evaluates `s.b = 2` against the `s.a` Bloom; a Row 
Group containing `(1,2)` can therefore be reported as `kNoMatch` (or 
incompatible leaf types can hit the type check). If a Bloom-capable child does 
not resolve to the same unique probe, please make the whole compound 
ineligible, and cover same- and mixed-type sibling leaves.



##########
be/src/exprs/expr_zonemap_filter.cpp:
##########
@@ -213,6 +246,113 @@ std::optional<SlotLiteral> extract_slot_and_literal(const 
VExprSPtrs& args) {
     return std::nullopt;
 }
 
+std::optional<BloomFilterProbe> extract_bloom_filter_probe(const VExprSPtr& 
expr) {
+    if (expr == nullptr || expr->data_type() == nullptr) {
+        return std::nullopt;
+    }
+    if (auto slot = std::dynamic_pointer_cast<VSlotRef>(expr); slot) {
+        return BloomFilterProbe {
+                .slot_index = slot->column_id(), .value_type = 
slot->data_type(), .path = {}};
+    }
+    if ((expr->fn().name.function_name != "element_at" &&
+         expr->fn().name.function_name != "struct_element") ||
+        expr->get_num_children() != 2) {
+        return std::nullopt;
+    }
+
+    auto probe = extract_bloom_filter_probe(expr->get_child(0));
+    auto selector = field_from_literal_expr(expr->get_child(1));
+    if (!probe.has_value() || !selector.has_value() || 
selector->first.is_null()) {
+        return std::nullopt;
+    }
+    const auto parent_type = remove_nullable(expr->get_child(0)->data_type());
+    if (parent_type == nullptr) {
+        return std::nullopt;
+    }
+
+    BloomFilterPathElement path_element;
+    switch (parent_type->get_primitive_type()) {
+    case TYPE_STRUCT: {
+        path_element.kind = BloomFilterPathKind::STRUCT_FIELD;
+        const auto selector_type = remove_nullable(selector->second);
+        if (selector_type == nullptr) {
+            return std::nullopt;
+        }
+        if (is_string_type(selector_type->get_primitive_type())) {
+            path_element.field_name = selector->first.get<TYPE_STRING>();
+        } else {
+            auto ordinal = struct_field_ordinal(selector->first);
+            if (!ordinal.has_value()) {
+                return std::nullopt;
+            }
+            path_element.field_ordinal = *ordinal;
+        }
+        break;
+    }
+    case TYPE_ARRAY:
+        // Array element positions share one repeated Parquet leaf; membership 
in that leaf is a
+        // necessary condition for any element_at(array, constant) equality to 
match.
+        path_element.kind = BloomFilterPathKind::LIST_ELEMENT;
+        break;
+    default:
+        return std::nullopt;
+    }
+    probe->value_type = expr->data_type();
+    probe->path.push_back(std::move(path_element));
+    return probe;
+}
+
+std::optional<BloomFilterProbe> extract_bloom_filter_predicate_probe(const 
VExprSPtr& expr) {
+    if (auto probe = extract_bloom_filter_probe(expr); probe.has_value()) {
+        return probe;
+    }
+    if (expr == nullptr) {
+        return std::nullopt;
+    }
+    std::optional<BloomFilterProbe> result;
+    for (uint16_t child_idx = 0; child_idx < expr->get_num_children(); 
++child_idx) {
+        const auto& child = expr->get_child(child_idx);
+        if (child == nullptr || child->is_literal()) {
+            continue;
+        }
+        auto child_probe = extract_bloom_filter_predicate_probe(child);
+        if (!child_probe.has_value()) {
+            continue;
+        }
+        if (result.has_value() && !bloom_filter_probes_equal(*result, 
*child_probe)) {
+            return std::nullopt;
+        }
+        result = std::move(child_probe);
+    }
+    return result;
+}
+
+std::optional<SlotLiteral> extract_bloom_filter_slot_and_literal(const 
VExprSPtrs& args) {
+    if (args.size() != 2) {
+        return std::nullopt;
+    }
+    for (size_t probe_idx = 0; probe_idx < args.size(); ++probe_idx) {
+        auto probe = extract_bloom_filter_probe(args[probe_idx]);
+        auto literal = field_from_literal_expr(args[1 - probe_idx]);
+        if (!probe.has_value() || !literal.has_value()) {
+            continue;
+        }
+        auto [literal_value, literal_type] = std::move(*literal);
+        return SlotLiteral {.slot_index = probe->slot_index,
+                            .slot_type = probe->value_type,
+                            .literal = std::move(literal_value),
+                            .literal_type = std::move(literal_type),
+                            .literal_on_left = probe_idx == 1};
+    }
+    return std::nullopt;
+}
+
+bool can_evaluate_bloom_filter_equality(const VExprSPtrs& args) {
+    auto slot_literal = extract_bloom_filter_slot_and_literal(args);
+    return slot_literal.has_value() && !slot_literal->literal.is_null() &&

Review Comment:
   [P1] Preserve FLOAT/DOUBLE equality classes when probing Parquet Blooms
   
   This capability now admits nested FLOAT/DOUBLE equality (and is reused by 
null-safe equality), but the native adapter hashes the literal's raw IEEE 
bytes. Doris considers `+0.0 == -0.0` and equates NaNs, while Parquet Bloom 
filters hash their distinct PLAIN encodings, so a Bloom containing `-0.0` can 
reject a `+0.0` probe and falsely skip a matching Row Group. Please probe both 
zero encodings and conservatively return `kMayMatch` for NaN, or disable 
FLOAT/DOUBLE Bloom pruning; add an external-writer signed-zero/NaN regression.



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