viirya opened a new pull request, #59848:
URL: https://github.com/apache/spark/pull/59848

   ### What changes were proposed in this pull request?
   
   `PushPredicateThroughNonJoin` reorders the conjuncts of a `Filter` over a 
`Project` that has expensive (UDF, expensive regex) aliases. It does so in two 
places, and neither checks `Expression.throwable`:
   
   1. The projection split added by SPARK-55014 
(`spark.sql.optimizer.splitProjectionForExpensiveFilters`). It orders condition 
groups by how many not-yet-computed aliases they need, so a `raise_error` / 
`assert_true` condition can be split below the conditions written before it.
   2. The cheap/expensive partition added by SPARK-47672 
(`spark.sql.optimizer.avoidDoubleFilterEval`). It pushes a cheap conjunct below 
the projection, ahead of an expensive conjunct written before it.
   
   This PR:
   - keeps throwable conditions out of the split, using `deterministic && 
!throwable`, the same check `PushPredicateThroughJoin` uses;
   - pushes a cheap throwable condition below the projection only when no 
expensive condition comes before it;
   - keeps the conditions left in the top filter in the order they were written.
   
   Moving a non-throwable condition below a throwable one is still allowed, 
since it can only reduce the rows the throwable condition sees. 
`CombineFilters` and the join pushdown rules already behave this way.
   
   ### Why are the changes needed?
   
   A throwable condition can be evaluated on rows that the `AND` short-circuit 
would have kept from it, so a query that should succeed fails. With `f` and `g` 
expensive UDFs:
   
   ```scala
   spark.range(0, 10)
     .select($"id", f($"id") as "f", g($"id") as "g")
     .where($"f" + $"g" > 10 && assert_true($"g" > 5).isNull)
   ```
   
   The `assert_true` group needs fewer aliases, so the split evaluates it 
first, on every row, and the query fails with `USER_RAISED_EXCEPTION`. It 
succeeds with `splitProjectionForExpensiveFilters=false`.
   
   ```scala
   spark.range(0, 10)
     .select($"id", f($"id") as "f")
     .where($"f" > 5 && assert_true($"id" > 5).isNull)
   ```
   
   Here the cheap `assert_true` condition is pushed below the projection, ahead 
of `f > 5`. This fails whenever `avoidDoubleFilterEval` is true, whatever the 
split config, and it is already in 4.2.0.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. Queries like the ones above failed with `USER_RAISED_EXCEPTION` in 
4.2.0 and later (the second shape), and on unreleased master / branch-4.x (the 
first shape). They now succeed. Filters without throwable conditions are 
optimized as before.
   
   ### How was this patch tested?
   
   New tests:
   - three tests in `FilterPushdownSuite` for the split, for a throwable 
condition not blocking later conditions, and for the cheap pushdown path; all 
three fail without this change;
   - an end-to-end test in `DataFrameSuite` that runs both queries above over 
every combination of whole-stage codegen, `splitProjectionForExpensiveFilters` 
and `avoidDoubleFilterEval`.
   
   All tests in `org.apache.spark.sql.catalyst.optimizer` pass.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5.5)
   
   This pull request and its description were written by Isaac.
   


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