You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by vi...@apache.org on 2025/08/23 19:12:32 UTC
(spark) branch branch-4.0 updated: [SPARK-53360][SQL] Once strategy with ConstantFolding's idempotence should not be broken
This is an automated email from the ASF dual-hosted git repository.
viirya pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.0 by this push:
new 326052ec8280 [SPARK-53360][SQL] Once strategy with ConstantFolding's idempotence should not be broken
326052ec8280 is described below
commit 326052ec8280d8bf8ee1904504be3b62a72d3d29
Author: Liang-Chi Hsieh <vi...@gmail.com>
AuthorDate: Sat Aug 23 12:07:22 2025 -0700
[SPARK-53360][SQL] Once strategy with ConstantFolding's idempotence should not be broken
This patch fixes the optimization rule `ConstantFolding`'s idempotence.
Currently the optimization rule `ConstantFolding` is not idempotent. So if the rule is used in any batch with `Once` strategy like `ConstantFoldingSuite` did, the `Once` strategy's idempotence will be broken. We encounter this issue internally.
No
Unit test
No
Closes #52106 from viirya/SPARK-53360.
Authored-by: Liang-Chi Hsieh <vi...@gmail.com>
Signed-off-by: Liang-Chi Hsieh <vi...@gmail.com>
(cherry picked from commit e28f42700611f6acd2de7dc84ecdf4f5b86b8f71)
Signed-off-by: Liang-Chi Hsieh <vi...@gmail.com>
---
.../spark/sql/catalyst/optimizer/expressions.scala | 33 ++++++++++++++--------
.../catalyst/optimizer/ConstantFoldingSuite.scala | 13 +++++++++
2 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/expressions.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/expressions.scala
index e867953bcf28..fbb9afe46860 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/expressions.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/expressions.scala
@@ -58,6 +58,19 @@ object ConstantFolding extends Rule[LogicalPlan] {
case _ => false
}
+ private def tryFold(expr: Expression, isConditionalBranch: Boolean): Expression = {
+ try {
+ Literal.create(expr.freshCopyIfContainsStatefulExpression().eval(EmptyRow), expr.dataType)
+ } catch {
+ case NonFatal(_) if isConditionalBranch =>
+ // When doing constant folding inside conditional expressions, we should not fail
+ // during expression evaluation, as the branch we are evaluating may not be reached at
+ // runtime, and we shouldn't fail the query, to match the original behavior.
+ expr.setTagValue(FAILED_TO_EVALUATE, ())
+ expr
+ }
+ }
+
private def constantFolding(
e: Expression,
isConditionalBranch: Boolean = false): Expression = e match {
@@ -77,17 +90,7 @@ object ConstantFolding extends Rule[LogicalPlan] {
case e if e.getTagValue(FAILED_TO_EVALUATE).isDefined => e
// Fold expressions that are foldable.
- case e if e.foldable =>
- try {
- Literal.create(e.freshCopyIfContainsStatefulExpression().eval(EmptyRow), e.dataType)
- } catch {
- case NonFatal(_) if isConditionalBranch =>
- // When doing constant folding inside conditional expressions, we should not fail
- // during expression evaluation, as the branch we are evaluating may not be reached at
- // runtime, and we shouldn't fail the query, to match the original behavior.
- e.setTagValue(FAILED_TO_EVALUATE, ())
- e
- }
+ case e if e.foldable => tryFold(e, isConditionalBranch)
// Don't replace ScalarSubquery if its plan is an aggregate that may suffer from a COUNT bug.
case s @ ScalarSubquery(_, _, _, _, _, mayHaveCountBug, _)
@@ -99,7 +102,13 @@ object ConstantFolding extends Rule[LogicalPlan] {
case s: ScalarSubquery if s.plan.maxRows.contains(0) =>
Literal(null, s.dataType)
- case other => other.mapChildren(constantFolding(_, isConditionalBranch))
+ case other =>
+ val newOther = other.mapChildren(constantFolding(_, isConditionalBranch))
+ if (newOther.foldable) {
+ tryFold(newOther, isConditionalBranch)
+ } else {
+ newOther
+ }
}
def apply(plan: LogicalPlan): LogicalPlan = plan.transformWithPruning(AlwaysProcess.fn, ruleId) {
diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/ConstantFoldingSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/ConstantFoldingSuite.scala
index d8d58ea6aa90..0489308a1ab2 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/ConstantFoldingSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/ConstantFoldingSuite.scala
@@ -437,6 +437,19 @@ class ConstantFoldingSuite extends PlanTest {
Optimize.execute(oneRowScalarSubquery),
oneRowScalarSubquery)
}
+
+ test("SPARK-53360: Once strategy with ConstantFolding's idempotence should not be broken") {
+ val emptyRelation = LocalRelation($"a".int)
+ val nullIntLit = Literal(null, IntegerType)
+
+ Seq(EqualTo, LessThan, GreaterThan).foreach { comparison =>
+ comparePlans(
+ Optimize.execute(testRelation
+ .select(comparison($"a",
+ Multiply(ScalarSubquery(emptyRelation), Literal(1, IntegerType))).as("o")).analyze),
+ testRelation.select(comparison($"a", nullIntLit).as("o")).analyze)
+ }
+ }
}
case class SerializableBoxedInt(intVal: Int) {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org