diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala index f4408220ac939..bd8f8fe9f6528 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/CheckAnalysis.scala @@ -267,6 +267,7 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB // Early checks for column definitions, to produce better error messages ColumnDefinition.checkColumnDefinitions(operator) + var stagedError: Option[() => Unit] = None getAllExpressions(operator).foreach(_.foreachUp { case a: Attribute if !a.resolved => failUnresolvedAttribute(operator, a, "UNRESOLVED_COLUMN") @@ -305,12 +306,14 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB s"Cannot resolve the runtime replaceable expression ${toSQLExpr(e)}. " + s"The replacement is unresolved: ${toSQLExpr(e.replacement)}.") + // `Grouping` and `GroupingID` are considered as of having lower priority than the other + // nodes which cause errors. case g: Grouping => - g.failAnalysis( - errorClass = "UNSUPPORTED_GROUPING_EXPRESSION", messageParameters = Map.empty) + if (stagedError.isEmpty) stagedError = Some(() => g.failAnalysis( + errorClass = "UNSUPPORTED_GROUPING_EXPRESSION", messageParameters = Map.empty)) case g: GroupingID => - g.failAnalysis( - errorClass = "UNSUPPORTED_GROUPING_EXPRESSION", messageParameters = Map.empty) + if (stagedError.isEmpty) stagedError = Some(() => g.failAnalysis( + errorClass = "UNSUPPORTED_GROUPING_EXPRESSION", messageParameters = Map.empty)) case e: Expression if e.children.exists(_.isInstanceOf[WindowFunction]) && !e.isInstanceOf[WindowExpression] && e.resolved => @@ -369,6 +372,7 @@ trait CheckAnalysis extends PredicateHelper with LookupCatalog with QueryErrorsB case _ => }) + if (stagedError.isDefined) stagedError.get.apply() operator match { case RelationTimeTravel(u: UnresolvedRelation, _, _) => diff --git a/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryCompilationErrorsSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryCompilationErrorsSuite.scala index 4574d3328d48a..958d2b0130d8b 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryCompilationErrorsSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/errors/QueryCompilationErrorsSuite.scala @@ -926,6 +926,37 @@ class QueryCompilationErrorsSuite parameters = Map("message" -> "Cannot convert Spark data type \"DUMMY\" to any Parquet type.") ) } + + test("SPARK-48556: Ensure UNRESOLVED_COLUMN is thrown when query has grouping expressions " + + "with invalid column name") { + case class UnresolvedDummyColumnTest(query: String, pos: Int) + + withTable("t1") { + sql("create table t1(a int, b int) using parquet") + val tests = Seq( + UnresolvedDummyColumnTest("select grouping(a), dummy from t1 group by a with rollup", 20), + UnresolvedDummyColumnTest("select dummy, grouping(a) from t1 group by a with rollup", 7), + UnresolvedDummyColumnTest( + "select a, case when grouping(a) = 1 then 0 else b end, count(dummy) from t1 " + + "group by 1 with rollup", + 61), + UnresolvedDummyColumnTest( + "select a, max(dummy), case when grouping(a) = 1 then 0 else b end " + + "from t1 group by 1 with rollup", + 14) + ) + tests.foreach(test => { + checkError( + exception = intercept[AnalysisException] { + sql(test.query) + }, + errorClass = "UNRESOLVED_COLUMN.WITH_SUGGESTION", + parameters = Map("objectName" -> "`dummy`", "proposal" -> "`a`, `b`"), + context = ExpectedContext(fragment = "dummy", start = test.pos, stop = test.pos + 4) + ) + }) + } + } } class MyCastToString extends SparkUserDefinedFunction(