andygrove commented on PR #4982:
URL: 
https://github.com/apache/datafusion-comet/pull/4982#issuecomment-5059344375

   _Review assisted by an LLM (Claude); a human reviewed the findings before 
posting._
   
   Thanks for addressing the earlier round. I confirmed all four are resolved: 
the stale `planner.rs` banner and leftover docstring are gone, byte/short widen 
to `int_val`, the nested-column invariant is documented, and `binary` is in the 
page-index gate with a good explanation of the UTF-8 column-index decode. The 
dedup design reads well, and the new `residual pool reflects whether the column 
type is pushable` test is the right addition since the correctness fuzz test 
structurally cannot observe pushdown.
   
   One new thing stood out on a closer read of the Rust side.
   
   ### `And`/`Not` in `iceberg_predicate_to_predicate` is unsafe under negation
   
   At `native/core/src/execution/planner.rs:4150-4169`, the Scala 
`icebergExprToProto` is deliberately whole-or-nothing on `And`: if either 
conjunct fails to convert, it elides the entire residual, and the comment 
explains why. Dropping a conjunct is safe in positive position but strengthens 
the predicate under a `NOT` via De Morgan (`NOT(A AND B)` = `NOT A OR NOT B`), 
which would wrongly prune. The Rust `Node::And` does the opposite. It keeps the 
surviving side when one is `None`, with the comment "Dropping a conjunct only 
weakens the pruning predicate, so a missing side is safe." That is only true in 
positive position. Since `Node::Not` just negates its child, `Not(And(Some, 
None))` evaluates to `!p` rather than `!a OR !b`, which is strictly stronger 
and would prune row groups that contain matching rows.
   
   This is not a live bug today. It is guarded by the Scala side never emitting 
a partial `And` and by `iceberg_literal_to_datum` never returning `None` for a 
value the serde actually produces. And these `Not(And(...))` shapes do reach 
Rust, since the `NOT` is only pushed to negation-normal form by `rewrite_not()` 
after decode, not in Scala. The concern is that the two sides evolve 
independently and the Rust code carries an unsafe invariant with a comment 
asserting it is safe. If a future change adds an `IcebergLiteral` variant on 
the Scala serialize path before the matching `iceberg_literal_to_datum` arm 
exists, a residual shaped `Not(A AND newtype_leaf)` would silently mis-prune.
   
   Since the Rust `And`-drop branch provides no benefit today (Scala guarantees 
both children are present), mirroring Scala's whole-or-nothing in Rust would be 
a strictly safer no-op that removes the footgun:
   
   ```rust
   match (left, right) {
       (Some(l), Some(r)) => Some(l.and(r)),
       _ => None,
   }
   ```
   
   At minimum the comment is worth correcting to note the positive-position 
assumption and why it holds.
   
   If it is cheap, a residual-level correctness test for `NOT(a AND b)` with 
both leaves pushable would exercise this exact shape. The existing `NOT over a 
partially supported AND does not drop rows` test covers the elision path but 
not the both-convert-under-`NOT` path.
   
   None of this blocks the PR. The overflow fix is correct and the 
instrumentation-driven diagnosis was great to follow.
   


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