HyukjinKwon commented on code in PR #59186:
URL: https://github.com/apache/spark/pull/59186#discussion_r4156480875


##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/InferFiltersFromConstraintsSuite.scala:
##########
@@ -465,4 +465,39 @@ class InferFiltersFromConstraintsSuite extends PlanTest {
     comparePlans(optimized, correctAnswer)
   }
 
+  test("SPARK-59922: an inferred filter does not carry a With") {
+    val x = testRelation.subquery("x")
+    val y = testRelation.subquery("y")
+
+    // The left user filter keeps a With inside a conditional branch. The 
branch references
+    // only x.a, an equi-join key, so InferFiltersFromConstraints substitutes 
it through the
+    // key and plants a matching filter on the right (over y.a). A With is 
opaque to filter
+    // pushdown, so the inferred filter must not carry one -- the user's own 
filter still may.
+    val xa = x.output.head
+    val branchWith = With(xa + xa) { case Seq(r) => (r > 1) && (r < 10) }
+    val leftPredicate = CaseWhen(Seq((xa > 0) -> branchWith), 
Some(Literal(false)))
+
+    val originalQuery = x.where(leftPredicate)
+      .join(y, Inner, Some("x.a".attr === "y.a".attr))
+      .analyze
+
+    // The right relation's equi-join key; exprIds are stable through 
optimization.
+    val ya = originalQuery.collectFirst { case j: Join => j.right.output.head }
+      .getOrElse(fail("expected a Join in the analyzed plan"))
+
+    val optimized = Optimize.execute(originalQuery)
+
+    // Conditions over the right side that carry the propagated CaseWhen 
branch predicate.
+    val inferred = optimized.collect {
+      case Filter(cond, _) if cond.references.contains(ya) => cond
+    }.filter(_.exists(_.isInstanceOf[CaseWhen]))
+
+    // PREMISE: the branch predicate was inferred and planted on the right 
side.
+    assert(inferred.nonEmpty, s"no inferred branch filter over the right 
side:\n$optimized")
+
+    // The inferred filter must not carry a With.
+    assert(inferred.forall(cond => !cond.exists(_.isInstanceOf[With])),

Review Comment:
   **Nit (P3):** This assertion only checks that no `With` node survives. If 
`inlineDefinitions` returned `withExpr.child` unchanged, the right-side filter 
would still contain a `CaseWhen` with no `With`, just with dangling 
`CommonExpressionRef`s, and the test would pass even though the plan can't be 
evaluated. The test also only drives the `inferNewFilter` path. Nothing covers 
the Filter-node site (`Filter(And(inlineWiths(newFilters.reduce(And)), 
condition), child)`), which is reached by e.g. `x.a = x.b AND CASE WHEN x.a > 0 
THEN With(x.a + x.a)(...) END`, or a nested `With` whose inner child references 
an enclosing definition.
   
   Could you compare against the expected inlined predicate with `comparePlans` 
(as the rest of this suite does), or at least assert that no 
`CommonExpressionRef` remains? It would also help to add a Filter-node case and 
a nested-`With` case.
   
   **Verification:**
   - **Regression:** The join-side case checks the exact inlined predicate (or 
the absence of any CommonExpressionRef), so a dangling-ref substitution fails.
   - **Behavior:** A single-filter case shows the inferred, substituted 
predicate planted by the Filter-node site is inlined.
   - **Behavior:** A nested-With case shows inner and enclosing definitions are 
both substituted in the planted predicate.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala:
##########
@@ -2303,9 +2303,30 @@ object InferFiltersFromConstraints extends 
Rule[LogicalPlan]
     if (newPredicates.isEmpty) {
       plan
     } else {
-      Filter(newPredicates.reduce(And), plan)
+      Filter(inlineWiths(newPredicates.reduce(And)), plan)
     }
   }
+
+  /**
+   * Inlines any `With` in an inferred predicate. `RewriteWithExpression` 
keeps a `With` where it
+   * cannot pre-evaluate the definition -- a conditional branch, for one -- 
and such a `With` can
+   * reach a constraint and be carried, through an equi-join key, into a 
filter inferred for the
+   * other side. A `With` is opaque to filter pushdown (`DataSourceStrategy` 
cannot translate one),

Review Comment:
   **Nit (P3):** `DataSourceStrategy` is the wrong consumer to name here. The 
only `With` that reaches this point sits inside a conditional branch, so even 
after inlining, the predicate still contains a `CaseWhen`/`If`, and 
`DataSourceStrategy.translateFilter` has no case for those. V1/file-source 
pushdown therefore isn't unblocked. The consumer that actually benefits is V2 
predicate translation: `V2ExpressionBuilder` translates `CaseWhen` but has no 
case for `With`. Could you reword this to name V2 predicate pushdown, so 
readers don't infer a V1 benefit?
   
   **Verification:**
   - **Inspection:** The comment's stated consumer agrees with the translation 
code: V2ExpressionBuilder translates CaseWhen, and DataSourceStrategy has no 
CaseWhen/If translation.



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