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

   ### What changes were proposed in this pull request?
   
   `V2ExpressionBuilder.generateExpression` handles a context independent 
foldable
   expression by constant folding it and recursing on the result, so that the 
folded
   value is translated by the `Literal` case:
   
   ```scala
   case _ if expr.contextIndependentFoldable && <datasourceV2ExprFolding> =>
     val constantExpr = ConstantFolding.constantFolding(expr)
     generateExpression(constantExpr, isPredicate)
   ```
   
   That assumes constant folding returns a literal. It does not. 
`ConstantFolding`
   returns an expression unchanged when it carries the `FAILED_TO_EVALUATE` 
tag, which
   is set when evaluation failed inside a conditional branch so that the error 
is not
   raised at planning time for a branch that may never be reached. The 
recursive call
   then re-enters the same case with the same expression and never makes 
progress.
   Because the call is in tail position it loops instead of raising
   `StackOverflowError`, so the symptom is a query that never returns.
   
   This PR recurses only when folding actually changed the expression.
   
   This follows SPARK-50380, which made `ReorderAssociativeOperator` stop 
assuming that
   a foldable expression folds to a literal.
   
   The guard is on the folding result rather than on the tag, so it also covers 
any
   future case where `constantFolding` returns its input unchanged.
   
   ### Why are the changes needed?
   
   With default settings, a pushdown eligible query against a DSv2 source hangs 
when a
   filter contains an expression that fails to evaluate inside a conditional 
branch:
   
   ```sql
   SET 
spark.sql.catalog.d=org.apache.spark.sql.execution.datasources.v2.jdbc.JDBCTableCatalog;
   SET spark.sql.catalog.d.url=jdbc:derby:memory:v2loopdb;
   SET spark.sql.catalog.d.driver=org.apache.derby.jdbc.EmbeddedDriver;
   SET spark.sql.catalog.d.create=true;
   
   CREATE NAMESPACE IF NOT EXISTS d.test;
   CREATE TABLE d.test.t (c INT);
   
   SELECT count(*) FROM d.test.t WHERE c = 1;              -- control: 0
   SELECT * FROM d.test.t WHERE coalesce(c, 1 div 0) = 1;  -- never returns
   ```
   
   Four conditions have to hold, which is why this was not hit earlier:
   
   - ANSI mode, so `1 div 0` throws while the planner folds it
   - the failing expression inside a conditional, so the error is deferred and 
tagged
     rather than raised
   - a DSv2 source that pushes predicates, since `V2ExpressionBuilder` is only 
used on
     that path
   - `spark.sql.optimizer.datasourceV2ExprFolding`, which defaults to true and 
was
     added in 4.1.0
   
   Setting `spark.sql.optimizer.datasourceV2ExprFolding` to false avoids the 
hang.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. A query of the shape above previously hung and now completes. Such an
   expression is no longer translated, so the predicate is not pushed to the 
source and
   is evaluated by Spark instead. Query results are unchanged.
   
   ### How was this patch tested?
   
   Added a test to `DataSourceV2StrategySuite`, next to the existing
   `datasourceV2ExprFolding` test. It builds `coalesce(c, 1 div 0) = 1`, 
asserts that
   constant folding tagged the failing branch, and then asserts that translation
   produces no V2 expression. The tag assertion is there so that the test 
cannot pass
   for the wrong reason if the tagging behaviour changes.
   
   Without this change the test does not terminate. The recursion is in tail 
position
   and does not block, so it cannot be interrupted by a time limit, which is 
why the
   test asserts the result instead of using `failAfter`. This matches existing 
tests
   for similar issues, for example "SPARK-48843: Prevent infinite loop with
   BindParameters" in `ParametersSuite`.
   
   ```
   build/sbt 'sql/testOnly *DataSourceV2StrategySuite *JDBCV2Suite'
   ```
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5)


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