M-Tesla commented on code in PR #3144:
URL: https://github.com/apache/iceberg-rust/pull/3144#discussion_r3973611244


##########
crates/iceberg/src/expr/visitors/row_group_metrics_evaluator.rs:
##########
@@ -476,28 +476,18 @@ impl BoundPredicateVisitor for 
RowGroupMetricsEvaluator<'_> {
             return ROW_GROUP_MIGHT_MATCH;
         }
 
-        if let Some(lower_bound) = self.min_value(field_id)? {
-            if lower_bound.is_nan() {
-                // NaN indicates unreliable bounds. See the 
InclusiveMetricsEvaluator docs for more.
-                return ROW_GROUP_MIGHT_MATCH;
-            }
+        let lower_bound = self.min_value(field_id)?;
+        let upper_bound = self.max_value(field_id)?;
 
-            if !literals.iter().any(|datum| datum.ge(&lower_bound)) {
-                // if all values are less than lower bound, rows cannot match.
-                return ROW_GROUP_CANT_MATCH;
-            }
+        if lower_bound.as_ref().is_some_and(|d| d.is_nan())

Review Comment:
   You are right, and thank you for walking through the old versus new control 
flow. I agree with treating a NaN bound as unbounded on that side rather than 
restoring the sequential bail.
   
   `eq` still returns might-match when the first bound is NaN, even if the 
other bound would prune. For `in` that is the wrong tradeoff: a reliable lower 
of 4.0 with a NaN upper should still reject `IN (2.0, 3.0)`. I drop the NaN 
bound to `None` and let `any_literal_in_bounds` handle `(Some, None)` / `(None, 
Some)`. If you would rather keep `in` identical to `eq`'s early exit, say so 
and I will switch it.



##########
crates/iceberg/src/expr/visitors/inclusive_metrics_evaluator.rs:
##########
@@ -437,28 +437,16 @@ impl BoundPredicateVisitor for 
InclusiveMetricsEvaluator<'_> {
             return ROWS_MIGHT_MATCH;
         }
 
-        if let Some(lower_bound) = self.lower_bound(field_id) {
-            if lower_bound.is_nan() {
-                // NaN indicates unreliable bounds. See the 
InclusiveMetricsEvaluator docs for more.
-                return ROWS_MIGHT_MATCH;
-            }
+        let lower_bound = self.lower_bound(field_id);
+        let upper_bound = self.upper_bound(field_id);
 
-            if !literals.iter().any(|datum| datum.ge(lower_bound)) {
-                // if all values are less than lower bound, rows cannot match.
-                return ROWS_CANNOT_MATCH;
-            }
+        if lower_bound.is_some_and(|d| d.is_nan()) || 
upper_bound.is_some_and(|d| d.is_nan()) {

Review Comment:
   Same change here. Inclusive now runs each bound through `finite_bound` 
before `any_literal_in_bounds`, so a valid bound still prunes when the other 
side is NaN.



##########
crates/iceberg/src/expr/visitors/row_group_metrics_evaluator.rs:
##########
@@ -1808,6 +1798,41 @@ mod tests {
         Ok(())
     }
 
+    #[test]

Review Comment:
   Agreed. I added that pin (lower = 4.0, upper = NaN, `IN (2.0, 3.0)` must 
prune), the inclusive counterpart, and helper tests for the one-sided arms. The 
old might-match NaN test really did pass under both implementations.



##########
crates/iceberg/src/expr/visitors/manifest_evaluator.rs:
##########
@@ -409,24 +409,18 @@ impl BoundPredicateVisitor for ManifestFilterVisitor<'_> {
             return ROWS_MIGHT_MATCH;
         }
 
-        if let Some(lower_bound) = &field.lower_bound {
-            let lower_bound = ManifestFilterVisitor::bytes_to_datum(
-                lower_bound,
-                *reference.field().clone().field_type,
-            );
-            if literals.iter().all(|datum| &lower_bound > datum) {
-                return ROWS_CANNOT_MATCH;
-            }
-        }
-
-        if let Some(upper_bound) = &field.upper_bound {
-            let upper_bound = ManifestFilterVisitor::bytes_to_datum(
-                upper_bound,
-                *reference.field().clone().field_type,
-            );
-            if literals.iter().all(|datum| &upper_bound < datum) {
-                return ROWS_CANNOT_MATCH;
-            }
+        let field_type = *reference.field().clone().field_type;

Review Comment:
   Good catch. Switched to `*reference.field().field_type.clone()` so we only 
clone the boxed type.



##########
crates/iceberg/src/expr/visitors/mod.rs:
##########
@@ -26,3 +30,20 @@ pub(crate) mod rewrite_not;
 pub(crate) mod row_group_metrics_evaluator;
 pub(crate) mod strict_metrics_evaluator;
 pub(crate) mod strict_projection;
+
+/// Returns true if any literal could match the inclusive `[lower, upper]` 
range.
+/// Missing bounds are treated as unbounded on that side.
+pub(crate) fn any_literal_in_bounds(
+    lower: Option<&Datum>,
+    upper: Option<&Datum>,
+    literals: &FnvHashSet<Datum>,
+) -> bool {
+    match (lower, upper) {
+        (Some(lower), Some(upper)) => literals
+            .iter()
+            .any(|datum| datum.ge(lower) && datum.le(upper)),
+        (Some(lower), None) => literals.iter().any(|datum| datum.ge(lower)),
+        (None, Some(upper)) => literals.iter().any(|datum| datum.le(upper)),
+        (None, None) => true,

Review Comment:
   Yes: if `field.lower_bound` is missing, `ManifestEvaluator::in` already 
returns cannot-match (all-null summary) and never reaches the helper. `(None, 
None)` is only a "no stats, cannot prune" signal for the metrics evaluators. I 
put that precondition on the helper so a later refactor cannot swap the two 
meanings.



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