diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala index abc282e9c488f..f918232c42ac9 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala @@ -300,12 +300,21 @@ class AstBuilder extends DataTypeAstBuilder parsingCtx) } else { // If there is no compound body, then there must be a statement or set statement. + // Single-statement handler bodies need a label for the CompoundBody, just like + // BEGIN-END blocks do (see visitBeginEndCompoundBlockImpl). Generate a random UUID + // label since no explicit label is defined. + val labelText = parsingCtx.labelContext.enterLabeledScope( + beginLabelCtx = None, + endLabelCtx = None + ) val statement = Option(ctx.statement().asInstanceOf[ParserRuleContext]) .orElse(Option(ctx.setStatementInsideSqlScript().asInstanceOf[ParserRuleContext])) .map { s => SingleStatement(parsedPlan = visit(s).asInstanceOf[LogicalPlan]) } - CompoundBody(Seq(statement.get), None, isScope = false) + val compoundBody = CompoundBody(Seq(statement.get), Some(labelText), isScope = false) + parsingCtx.labelContext.exitLabeledScope(None) + compoundBody } ExceptionHandler(exceptionHandlerTriggers, body, handlerType) diff --git a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala index d911862509aa0..2773023fafe75 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala @@ -1323,6 +1323,89 @@ class SqlScriptingExecutionSuite extends QueryTest with SharedSparkSession { verifySqlScriptResult(sqlScript, expected = expected) } + test("exit handler body without BEGIN-END propagates error properly") { + val sqlScript = + """ + |BEGIN + | DECLARE EXIT HANDLER FOR SQLEXCEPTION + | INSERT INTO test_table_non_existing VALUES(1, 2, 3); + | + | SELECT 1/0; + |END + |""".stripMargin + val exception = intercept[AnalysisException] { + verifySqlScriptResult(sqlScript, Seq.empty) + } + checkError( + exception = exception, + condition = "TABLE_OR_VIEW_NOT_FOUND", + sqlState = Some("42P01"), + parameters = Map("relationName" -> toSQLId("test_table_non_existing")), + context = ExpectedContext( + fragment = "test_table_non_existing", + start = 63, + stop = 85) + ) + } + + test("continue handler body without BEGIN-END propagates error properly") { + val sqlScript = + """ + |BEGIN + | DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + | INSERT INTO test_table_non_existing VALUES(1, 2, 3); + | + | SELECT 1/0; + |END + |""".stripMargin + val exception = intercept[AnalysisException] { + verifySqlScriptResult(sqlScript, Seq.empty) + } + checkError( + exception = exception, + condition = "TABLE_OR_VIEW_NOT_FOUND", + sqlState = Some("42P01"), + parameters = Map("relationName" -> toSQLId("test_table_non_existing")), + context = ExpectedContext( + fragment = "test_table_non_existing", + start = 67, + stop = 89) + ) + } + + test("exit handler body without BEGIN-END executes properly") { + val sqlScript = + """ + |BEGIN + | DECLARE EXIT HANDLER FOR SQLEXCEPTION + | SELECT 1; + | + | SELECT 1/0; + | SELECT 2; + |END + |""".stripMargin + val expected = Seq(Seq(Row(1))) + verifySqlScriptResult(sqlScript, expected) + } + + test("continue handler body without BEGIN-END executes properly") { + val sqlScript = + """ + |BEGIN + | DECLARE CONTINUE HANDLER FOR SQLEXCEPTION + | SELECT 1; + | + | SELECT 1/0; + | SELECT 2; + |END + |""".stripMargin + val expected = Seq( + Seq(Row(1)), // select from handler + Seq(Row(2)) // select + ) + verifySqlScriptResult(sqlScript, expected) + } + // Tests test("multi statement - simple") { withTable("t") {