This is an automated email from the ASF dual-hosted git repository.
cloud-fan pushed a commit to branch branch-4.3
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.3 by this push:
new 32274cda3af8 [SPARK-57353][SQL] Fix single-pass resolver crash on
GROUPING SETS with HAVING/ORDER BY
32274cda3af8 is described below
commit 32274cda3af8e1e26eadc13c8d7eeef7d6f63634
Author: Anupam Yadav <[email protected]>
AuthorDate: Tue Aug 11 15:41:33 2026 +0800
[SPARK-57353][SQL] Fix single-pass resolver crash on GROUPING SETS with
HAVING/ORDER BY
### What changes were proposed in this pull request?
Fix a crash in the single-pass resolver (Analyzer++) when GROUP BY
CUBE/ROLLUP/GROUPING SETS is used with HAVING, ORDER BY, or lateral column
aliases (LCA) containing aggregate functions.
The fix wires `GroupingAnalyticsResolver` into `AggregateResolver` to
expand `BaseGroupingSets` into an `Expand` operator before
`AggregationValidator` runs, and guards `ExprUtils.checkValidGroupingExprs`
against calling `.dataType` on unresolved grouping expressions. Post-expansion
validation is applied in both the LCA and non-LCA paths.
HAVING/ORDER BY/LCA combined with grouping analytics are not yet fully
supported in the single-pass resolver, so this PR converts the crash into a
clean unsupported-feature signal (`ExplicitlyUnsupportedResolverFeature`)
scoped to the direct aggregate lineage. Grouping analytics confined to a scalar
subquery or a derived table (SubqueryAlias, including CTEs and views) do not
affect unrelated outer Sort/Having in the enclosing query. Full support inside
the single-pass resolver is trac [...]
### Why are the changes needed?
With `spark.sql.analyzer.singlePassResolver.enabled=true`, queries like:
```sql
SELECT a, b, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
GROUP BY CUBE(a, b) ORDER BY SUM(b);
```
crash with:
```
org.apache.spark.SparkUnsupportedOperationException:
[UNSUPPORTED_CALL.WITHOUT_SUGGESTION]
Cannot call the method "dataType$" of the class
"org.apache.spark.sql.catalyst.expressions.BaseGroupingSets".
```
The single-pass resolver called `assertValidAggregation` while
`BaseGroupingSets` nodes were still present in the grouping expressions (the
legacy analyzer expands them via `ResolveGroupingAnalytics` before validation).
`BaseGroupingSets.dataType` throws by design because these nodes must be
expanded first.
### Does this PR introduce _any_ user-facing change?
Yes. GROUPING SETS/CUBE/ROLLUP queries with HAVING, ORDER BY, or lateral
column aliases no longer crash under the single-pass resolver.
- In tentative mode (`spark.sql.analyzer.singlePassResolver.enabled=true`
with fallback), the resolver signals these combinations as unsupported and
falls back to the fixed-point analyzer, returning correct results.
- In pure single-pass mode, they surface as an
`ExplicitlyUnsupportedResolverFeature` analysis error naming the specific
unsupported combination, rather than as an internal crash.
- Queries whose grouping analytics live entirely inside a scalar subquery
or a derived table are unaffected in the outer scope, so an outer ORDER BY or
HAVING that does not itself use grouping analytics is not rejected.
### How was this patch tested?
Added tests in `SQLQuerySuite` and
`ExplicitlyUnsupportedResolverFeatureSuite` covering:
- CUBE/ROLLUP/GROUPING SETS with ORDER BY and HAVING (tentative mode
returns results, matches the legacy analyzer baseline)
- HAVING/ORDER BY/LCA + grouping analytics rejected with
`ExplicitlyUnsupportedResolverFeature` in pure single-pass mode
- Grouping analytics confined to a scalar subquery or a derived table:
outer Sort and HAVING are not rejected (query-block-boundary regression)
- NULL grouping columns, empty results, multiple aggregates
- Negative test: column not in GROUP BY still errors with
`MISSING_AGGREGATION`
Note: a separate wrong-results issue (SPARK-57346) exists for full
single-pass support of these combinations and is out of scope for this crash
fix.
### Was this patch authored or co-authored using generative AI tooling?
Yes.
Closes #56417 from yadavay-amzn/fix/SPARK-57353-grouping-sets-crash.
Authored-by: Anupam Yadav <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
(cherry picked from commit e0a820234c95346d71d47d3f622ec2a83496db26)
Signed-off-by: Wenchen Fan <[email protected]>
---
.../analysis/resolver/AggregateResolver.scala | 28 +++-
.../analysis/resolver/HavingResolver.scala | 8 ++
.../resolver/LateralColumnAliasResolver.scala | 3 +
.../resolver/OperatorResolutionContext.scala | 17 ++-
.../catalyst/analysis/resolver/SortResolver.scala | 8 ++
.../scala/org/apache/spark/sql/SQLQuerySuite.scala | 157 +++++++++++++++++++++
...ExplicitlyUnsupportedResolverFeatureSuite.scala | 71 ++++++++++
7 files changed, 282 insertions(+), 10 deletions(-)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/AggregateResolver.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/AggregateResolver.scala
index cd51f17c9766..da9083f99fd2 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/AggregateResolver.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/AggregateResolver.scala
@@ -28,6 +28,7 @@ import org.apache.spark.sql.catalyst.expressions.{
Alias,
AliasHelper,
AttributeReference,
+ BaseGroupingSets,
Expression,
ExprId,
IntegerLiteral,
@@ -51,6 +52,8 @@ class AggregateResolver(
private val operatorResolutionContextStack =
operatorResolver.getOperatorResolutionContextStack
private val lcaResolver = expressionResolver.getLcaResolver
private val ordinalResolver = expressionResolver.getOrdinalResolver
+ private val groupingAnalyticsResolver =
+ new GroupingAnalyticsResolver(operatorResolver, expressionResolver)
/**
* Resolve [[Aggregate]] operator.
@@ -144,25 +147,36 @@ class AggregateResolver(
)
if (resolvedAggregateExpressions.hasLateralColumnAlias) {
+ // LCA + grouping analytics (CUBE/ROLLUP/GROUPING SETS) is not yet
supported in the
+ // single-pass resolver because Expand mints new attribute IDs that
get out of sync
+ // with the Project operator. Fall back to legacy analyzer for correct
results.
+ if
(finalAggregate.groupingExpressions.exists(_.isInstanceOf[BaseGroupingSets])) {
+ throw new ExplicitlyUnsupportedResolverFeature(
+ "lateral column alias with grouping analytics")
+ }
val aggregateWithLcaResolutionResult =
lcaResolver.handleLcaInAggregate(finalAggregate)
+ val lcaBaseAggregate = aggregateWithLcaResolutionResult.baseAggregate
AggregateResolutionResult(
operator = aggregateWithLcaResolutionResult.resolvedOperator,
outputList = aggregateWithLcaResolutionResult.outputList,
groupingAttributeIds =
-
getGroupingAttributeIds(aggregateWithLcaResolutionResult.baseAggregate),
+ getGroupingAttributeIds(lcaBaseAggregate),
aggregateListAliases =
aggregateWithLcaResolutionResult.aggregateListAliases,
- baseAggregate = aggregateWithLcaResolutionResult.baseAggregate
+ baseAggregate = lcaBaseAggregate
)
} else {
- AggregationValidator(finalAggregate)
+ // Grouping analytics (CUBE/ROLLUP/GROUPING SETS) are expanded
unconditionally.
+ // groupingAnalyticsResolver.resolve no-ops when no BaseGroupingSets
are present.
+ val expandedAggregate =
groupingAnalyticsResolver.resolve(finalAggregate)
+ AggregationValidator(expandedAggregate)
AggregateResolutionResult(
- operator = finalAggregate,
- outputList = finalAggregate.aggregateExpressions,
- groupingAttributeIds = getGroupingAttributeIds(finalAggregate),
+ operator = expandedAggregate,
+ outputList = expandedAggregate.aggregateExpressions,
+ groupingAttributeIds = getGroupingAttributeIds(expandedAggregate),
aggregateListAliases =
scopes.current.getTopAggregateExpressionAliases,
- baseAggregate = finalAggregate
+ baseAggregate = expandedAggregate
)
}
} finally {
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/HavingResolver.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/HavingResolver.scala
index 93c0eabb9487..ddf930ac7b1e 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/HavingResolver.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/HavingResolver.scala
@@ -49,6 +49,14 @@ class HavingResolver(resolver: Resolver, expressionResolver:
ExpressionResolver)
override def resolve(unresolvedHaving: UnresolvedHaving): LogicalPlan = {
val resolvedChild = resolver.resolve(unresolvedHaving.child)
+ // HAVING over grouping analytics (CUBE/ROLLUP/GROUPING SETS) is not yet
supported in
+ // single-pass because the expanded Aggregate's ExprIds get out of sync
when HAVING
+ // inserts missing expressions. Fall back to legacy for correct results
(SPARK-57346).
+ if (operatorResolutionContextStack.current.hasGroupingAnalytics) {
+ throw new ExplicitlyUnsupportedResolverFeature(
+ "HAVING with grouping analytics (SPARK-57346)")
+ }
+
resolvedChild match {
case window: Window if scopes.current.baseAggregate.isDefined =>
resolveHavingAboveWindow(
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/LateralColumnAliasResolver.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/LateralColumnAliasResolver.scala
index 3d19c4cbde8c..6859c98f1f99 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/LateralColumnAliasResolver.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/LateralColumnAliasResolver.scala
@@ -70,6 +70,9 @@ class LateralColumnAliasResolver(expressionResolver:
ExpressionResolver, operato
case _ @Project(projectList: Seq[_], aggregate: Aggregate) =>
operatorResolutionContextStack.current.baseOperator = Some(aggregate)
+ // Note: LCA + grouping analytics (CUBE/ROLLUP/GROUPING SETS) is
intercepted in
+ // AggregateResolver which throws ExplicitlyUnsupportedResolverFeature
before reaching
+ // here. This validation is retained as defense-in-depth in case the
call path changes.
AggregationValidator(aggregate)
val remappedAliases = new HashMap[ExprId, Alias](projectList.size)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/OperatorResolutionContext.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/OperatorResolutionContext.scala
index 5df31a9c09fd..880fce67ba67 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/OperatorResolutionContext.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/OperatorResolutionContext.scala
@@ -51,7 +51,8 @@ class OperatorResolutionContext(
var baseOperator: Option[LogicalPlan] = None,
val isResolvingTreeUnderHaving: Boolean = false,
var parameterNamesToValues: Option[LinkedHashMap[String, Expression]] =
None,
- var hasGroupingAnalytics: Boolean = false) {
+ var hasGroupingAnalytics: Boolean = false,
+ val isSubqueryRoot: Boolean = false) {
/**
* @param subqueryExpressionsToValidate List of [[SubqueryExpression]]s in
the context of current
@@ -130,7 +131,8 @@ class OperatorResolutionContextStack {
new OperatorResolutionContext(
unresolvedPlan = Some(unresolvedPlan),
isResolvingTreeUnderHaving = isResolvingTreeUnderHaving,
- parameterNamesToValues = current.parameterNamesToValues
+ parameterNamesToValues = current.parameterNamesToValues,
+ isSubqueryRoot = isSubqueryRoot
)
)
}
@@ -138,11 +140,20 @@ class OperatorResolutionContextStack {
/**
* Pops the top resolution context from the stack. Before popping,
propagates the
* `hasGroupingAnalytics` from the child context to the parent context if it
was set.
+ * The flag is NOT propagated across subquery boundaries (isSubqueryRoot) or
derived-table
+ * boundaries (SubqueryAlias), because grouping analytics inside a subquery
or derived table
+ * are unrelated to operators in the outer query.
*/
def pop(): Unit = {
val childContext = current
stack.pop()
- if (childContext.hasGroupingAnalytics) {
+ val isDerivedTableBoundary = childContext.unresolvedPlan match {
+ case Some(_: SubqueryAlias) => true
+ case _ => false
+ }
+ if (childContext.hasGroupingAnalytics &&
+ !childContext.isSubqueryRoot &&
+ !isDerivedTableBoundary) {
current.hasGroupingAnalytics = childContext.hasGroupingAnalytics
}
}
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/SortResolver.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/SortResolver.scala
index ed0edf90969f..1219d16e4c83 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/SortResolver.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/resolver/SortResolver.scala
@@ -135,6 +135,14 @@ class SortResolver(operatorResolver: Resolver,
expressionResolver: ExpressionRes
val resolvedChild = operatorResolver.resolve(unresolvedSort.child)
+ // ORDER BY over grouping analytics (CUBE/ROLLUP/GROUPING SETS) is not yet
supported in
+ // single-pass because the expanded Aggregate's ExprIds get out of sync
when ORDER BY
+ // inserts missing expressions. Fall back to legacy for correct results
(SPARK-57346).
+ if (operatorResolutionContextStack.current.hasGroupingAnalytics) {
+ throw new ExplicitlyUnsupportedResolverFeature(
+ "ORDER BY with grouping analytics (SPARK-57346)")
+ }
+
operatorResolutionContextStack.current.ordinalReplacementExpressions =
Some(
OrdinalReplacementSortOrderExpressions(
expressions = scopes.current.output.toIndexedSeq,
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala
index 03d3a122da16..8075a8a0776b 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala
@@ -5158,6 +5158,163 @@ class SQLQuerySuite extends SharedSparkSession with
AdaptiveSparkPlanHelper
sql("SELECT col1 - rand() FROM VALUES(1) GROUP BY ALL")
}
}
+
+ test("SPARK-57353: CUBE with ORDER BY - single pass resolver (tentative
fallback)") {
+ // ORDER BY + grouping analytics throws
ExplicitlyUnsupportedResolverFeature in pure
+ // single-pass (SPARK-57346). In tentative mode, the HybridAnalyzer falls
back to legacy.
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) as s FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a) ORDER BY s""".stripMargin),
+ Row(1, 30) :: Row(2, 30) :: Row(null, 60) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: ROLLUP with HAVING - single pass resolver (tentative
fallback)") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a) HAVING SUM(b) > 30""".stripMargin),
+ Row(null, 60) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: GROUPING SETS with ORDER BY - single pass resolver
(tentative fallback)") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, b, SUM(b) as s FROM VALUES (1,10),(1,20),(2,30) AS
t(a,b)
+ |GROUP BY a, b GROUPING SETS ((a, b), (a)) ORDER BY
s""".stripMargin),
+ Row(1, 10, 10) :: Row(1, 20, 20) :: Row(1, null, 30) ::
+ Row(2, 30, 30) :: Row(2, null, 30) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: CUBE with NULL grouping columns - single pass resolver
(tentative fallback)") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) as s FROM VALUES (1,10),(null,20),(2,30) AS
t(a,b)
+ |GROUP BY CUBE(a) ORDER BY s""".stripMargin),
+ Row(1, 10) :: Row(null, 20) :: Row(2, 30) :: Row(null, 60) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: ROLLUP with HAVING filtering all rows - single pass
resolver" +
+ " (tentative fallback)") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a) HAVING SUM(b) > 100""".stripMargin),
+ Nil)
+ }
+ }
+
+ test("SPARK-57353: CUBE with multiple aggregates in ORDER BY - single pass
resolver" +
+ " (tentative fallback)") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) as s, COUNT(b) as c
+ |FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a) ORDER BY COUNT(b), SUM(b)""".stripMargin),
+ Row(2, 30, 1) :: Row(1, 30, 2) :: Row(null, 60, 3) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: multi-column ROLLUP with HAVING - single pass resolver
(tentative fallback" +
+ ", SPARK-57346)") {
+ // SPARK-57346: multi-column ROLLUP with HAVING previously produced wrong
results (1 row
+ // instead of 4) in pure single-pass. Now throws
ExplicitlyUnsupportedResolverFeature so
+ // tentative mode falls back to legacy for correct results.
+ val query =
+ """SELECT a, b, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a, b) HAVING SUM(b) > 25""".stripMargin
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ val legacyResult = withSQLConf(
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key ->
"false") {
+ sql(query).collect().toSeq
+ }
+ checkAnswer(sql(query), legacyResult)
+ }
+ }
+
+ test("SPARK-57353: missing-aggregation still detected after GROUPING SETS
expansion") {
+ // Confirms aggregate-expression validation still fires: column 'b' is not
in GROUP BY
+ // ROLLUP(a) and is not aggregated, so MISSING_AGGREGATION should be
raised.
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "true") {
+ val ex = intercept[AnalysisException] {
+ sql(
+ """SELECT a, b, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a) HAVING SUM(b) > 10""".stripMargin).collect()
+ }
+ assert(ex.getCondition === "MISSING_AGGREGATION")
+ }
+ }
+
+ test("SPARK-57353: LCA with GROUPING SETS - single pass resolver") {
+ // LCA + grouping analytics throws ExplicitlyUnsupportedResolverFeature in
single-pass mode.
+ // In tentative mode, the HybridAnalyzer catches it and falls back to
legacy for correct
+ // results.
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false",
+ SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED_TENTATIVELY.key -> "true")
{
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) as total, total + 1
+ |FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a) ORDER BY total""".stripMargin),
+ Row(1, 30, 31) :: Row(2, 30, 31) :: Row(null, 60, 61) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: legacy analyzer handles CUBE/ROLLUP/GROUPING SETS
correctly") {
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "false") {
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) as s FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a) ORDER BY s""".stripMargin),
+ Row(1, 30) :: Row(2, 30) :: Row(null, 60) :: Nil)
+ checkAnswer(
+ sql(
+ """SELECT a, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a) HAVING SUM(b) > 30""".stripMargin),
+ Row(null, 60) :: Nil)
+ }
+ }
+
+ test("SPARK-57353: nested aggregate with GROUPING SETS is rejected") {
+ // Confirms aggregate-expression validation (nested agg check) still fires
with grouping sets.
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "true") {
+ val ex = intercept[AnalysisException] {
+ sql(
+ """SELECT a, SUM(COUNT(b)) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a)""".stripMargin).collect()
+ }
+ assert(ex.getCondition === "NESTED_AGGREGATE_FUNCTION")
+ }
+ }
+
+ test("SPARK-57353: GROUPING SETS with empty grouping list - all rows") {
+ // GROUPING SETS (()) produces a single grand-total row.
+ withSQLConf(SQLConf.ANALYZER_SINGLE_PASS_RESOLVER_ENABLED.key -> "true") {
+ QueryTest.checkAnswer(
+ sql(
+ """SELECT SUM(b) as s FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY a GROUPING SETS (())""".stripMargin),
+ Row(60) :: Nil,
+ checkToRDD = false)
+ }
+ }
}
case class Foo(bar: Option[String])
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/analysis/resolver/ExplicitlyUnsupportedResolverFeatureSuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/analysis/resolver/ExplicitlyUnsupportedResolverFeatureSuite.scala
index 3cf2613cf21d..a8c1041509cc 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/analysis/resolver/ExplicitlyUnsupportedResolverFeatureSuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/analysis/resolver/ExplicitlyUnsupportedResolverFeatureSuite.scala
@@ -45,6 +45,34 @@ class ExplicitlyUnsupportedResolverFeatureSuite extends
SharedSparkSession {
}
}
+ test("SPARK-57353: HAVING with grouping analytics is unsupported
(SPARK-57346)") {
+ checkResolution(
+ """SELECT a, SUM(b) FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY ROLLUP(a) HAVING SUM(b) > 30""".stripMargin,
+ shouldPass = false,
+ expectedMessage = Some("HAVING with grouping analytics (SPARK-57346)")
+ )
+ }
+
+ test("SPARK-57353: ORDER BY with grouping analytics is unsupported
(SPARK-57346)") {
+ checkResolution(
+ """SELECT a, SUM(b) as s FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a) ORDER BY s""".stripMargin,
+ shouldPass = false,
+ expectedMessage = Some("ORDER BY with grouping analytics (SPARK-57346)")
+ )
+ }
+
+ test("SPARK-57353: LCA with grouping analytics is unsupported") {
+ checkResolution(
+ """SELECT a, SUM(b) as total, total + 1
+ |FROM VALUES (1,10),(1,20),(2,30) AS t(a,b)
+ |GROUP BY CUBE(a)""".stripMargin,
+ shouldPass = false,
+ expectedMessage = Some("lateral column alias with grouping analytics")
+ )
+ }
+
test("ASOF JOIN MATCH_CONDITION operands requiring element-wise ordering") {
withSQLConf(SQLConf.SQL_ASOF_JOIN_ENABLED.key -> "true") {
checkResolution(
@@ -66,6 +94,49 @@ class ExplicitlyUnsupportedResolverFeatureSuite extends
SharedSparkSession {
}
}
+ test("SPARK-57353: ORDER BY with grouping analytics in subquery does not
reject outer sort") {
+ // Grouping analytics inside a scalar subquery must not leak
hasGroupingAnalytics to the
+ // outer query. The outer ORDER BY is unrelated and should resolve
successfully.
+ checkResolution(
+ """SELECT (SELECT SUM(x) FROM VALUES (1),(2) t(x)
+ | GROUP BY GROUPING SETS (())) AS s ORDER BY s""".stripMargin,
+ shouldPass = true
+ )
+ }
+
+ test("SPARK-57353: HAVING with grouping analytics in subquery does not
reject outer having") {
+ // Same boundary isolation for HAVING: grouping analytics confined to a
subquery must not
+ // cause the outer HAVING to be rejected.
+ checkResolution(
+ """SELECT a, (SELECT SUM(x) FROM VALUES (1),(2) t(x)
+ | GROUP BY GROUPING SETS (())) AS s
+ |FROM VALUES (1),(2),(3) v(a)
+ |GROUP BY a
+ |HAVING a > 1""".stripMargin,
+ shouldPass = true
+ )
+ }
+
+ test("SPARK-57353: HAVING with grouping analytics in derived table does not
reject outer") {
+ // Grouping analytics inside a derived table (SubqueryAlias) must not leak
+ // hasGroupingAnalytics to the outer query. The outer HAVING is unrelated.
+ checkResolution(
+ """SELECT a FROM (SELECT a FROM VALUES (1) t(a) GROUP BY CUBE(a)) s
+ |GROUP BY a HAVING a > 0""".stripMargin,
+ shouldPass = true
+ )
+ }
+
+ test("SPARK-57353: ORDER BY with grouping analytics in derived table does
not reject outer") {
+ // Same boundary isolation for ORDER BY: grouping analytics confined to a
derived table
+ // must not cause the outer ORDER BY to be rejected.
+ checkResolution(
+ """SELECT a FROM (SELECT a FROM VALUES (1) t(a) GROUP BY CUBE(a)) s
+ |ORDER BY a""".stripMargin,
+ shouldPass = true
+ )
+ }
+
private def checkResolution(
sqlText: String,
shouldPass: Boolean = false,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]