jayzhan211 commented on code in PR #25339:
URL: https://github.com/apache/datafusion/pull/25339#discussion_r4046914195


##########
datafusion/optimizer/src/decorrelate_predicate_subquery.rs:
##########
@@ -584,25 +583,12 @@ fn build_join(
             sub_query_alias.clone()
         };
 
-        let mark_filter_is_hashable_only =
-            if join_type == JoinType::LeftMark && in_predicate_opt.is_some() {
-                let (_, residual_filter) = split_eq_and_noneq_join_predicate(
-                    join_filter.clone(),
-                    left.schema(),
-                    right_projected.schema(),
-                )?;
-                residual_filter.is_none()
-            } else {
-                false
-            };
-
         // For scalar NOT IN mark joins, propagate null-aware semantics into 
the
-        // nullable mark column when the predicate can be implemented by hash 
keys.
-        // Non-equality correlated filters stay on the legacy path because 
hash join
-        // execution cannot mark UNKNOWN candidates for residual predicates.
+        // nullable mark column. A non-equality correlation stays behind as a
+        // join filter, which the hash join also applies when it decides
+        // whether a NULL makes the mark UNKNOWN.
         let null_aware = join_type == JoinType::LeftMark

Review Comment:
   This change also makes plain `IN` (not just `NOT IN`) null-aware when the 
subquery has a non-equality correlation; see the four changed plans in 
`subquery.slt`. In `WHERE a OR x IN (...)` the filter drops the row whether the 
mark is NULL or FALSE, so base was already correct for these queries. The 
null-aware plan only adds cost: it pins the outer table as the `CollectLeft` 
build side, cannot swap, and evaluates the join filter for every (build row × 
NULL probe row) pair.
   
   Measured (release-nonlto, 100k × 100k, same result 9900 on both): base 1–2 
ms → PR 7.4–8.1 s. At 30k rows it takes 0.57 s, so it is quadratic. The plan 
changes from `RightMark` to `LeftMark ... null_aware`.
   
   Repro:
   ```sql
   CREATE TABLE so AS SELECT value AS id_n0, value % 1000 AS z FROM range(0, 
100000);
   CREATE TABLE si AS SELECT CASE WHEN value % 2 = 0 THEN NULL ELSE value * 2 
END AS id_n50, value % 1000 AS z FROM range(0, 100000);
   SELECT count(*) FROM so o
   WHERE o.z > 900 OR o.id_n0 IN (SELECT i.id_n50 FROM si i WHERE i.z > o.z + 
990);
   ```
   
   Fix: only make the mark join null-aware when a NULL mark can behave 
differently from FALSE. That is never the case when the subquery is a 
non-negated `IN`/`EXISTS` sitting directly under `AND`/`OR` in a `WHERE` 
conjunct. A helper to detect that:
   ```rs
   /// True when every subquery in `expr` is a non-negated `IN`/`EXISTS` reached
   /// only through AND/OR. A filter treats a NULL mark like FALSE there, so the
   /// mark join does not need to be null-aware.
   fn subqueries_only_positive(expr: &Expr) -> bool {
       match expr {
           Expr::BinaryExpr(BinaryExpr {
               left,
               op: Operator::And | Operator::Or,
               right,
           }) => subqueries_only_positive(left) && 
subqueries_only_positive(right),
           Expr::InSubquery(InSubquery { negated, .. }) => !negated,
           Expr::Exists(Exists { negated, .. }) => !negated,
           other => !has_subquery(other),
       }
   }
   ```
   Call it on each conjunct in the `SubqueryPredicate::Embedded` arm and pass 
the result down `rewrite_inner_subqueries` → `mark_join` → `build_join` as a 
new `bool` (say `needs_null_aware_mark`). Callers outside a `Filter` pass 
`true`. Then:
   ```diff
            let null_aware = join_type == JoinType::LeftMark
                && in_predicate_opt.is_some()
   +            && needs_null_aware_mark
                && join_keys_may_be_null(
   ```
   `NOT IN`, `NOT (x IN ...)`, `(x IN ...) IS NULL`, `CASE` and projected marks 
stay null-aware, so the fix in this PR is unaffected. The four `subquery.slt` 
plan diffs should revert; please add an `EXPLAIN` test that pins the 
positive-`IN` plan as non-null-aware



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