This is an automated email from the ASF dual-hosted git repository.
wenchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 4435a3a7f088 [SPARK-54558][SQL] Fix Internal Exception when Exception
Handlers with no BEGIN/END are used
4435a3a7f088 is described below
commit 4435a3a7f0883559de3bd13ba84f311200943622
Author: Milan Dankovic <[email protected]>
AuthorDate: Mon Dec 1 16:38:59 2025 -0800
[SPARK-54558][SQL] Fix Internal Exception when Exception Handlers with no
BEGIN/END are used
### What changes were proposed in this pull request?
When Exception Handlers which don't have BEGIN-END body are triggered,
internal exception `java.util.NoSuchElementException` was thrown instead of
executing properly or propagating/raising the new error if it happens in
handler.
```
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
SELECT 1;
SELECT 1/0;
END
```
### Why are the changes needed?
Code was encountering a bug which throws internal error for what should be
valid user code.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
New unit tests in `SqlScriptingExecutionSuite`.
### Was this patch authored or co-authored using generative AI tooling?
No.
Closes #53271 from miland-db/milan-dankovic_data/fix-no-body-handlers.
Authored-by: Milan Dankovic <[email protected]>
Signed-off-by: Wenchen Fan <[email protected]>
---
.../spark/sql/catalyst/parser/AstBuilder.scala | 11 ++-
.../sql/scripting/SqlScriptingExecutionSuite.scala | 83 ++++++++++++++++++++++
2 files changed, 93 insertions(+), 1 deletion(-)
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 abc282e9c488..f918232c42ac 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 d911862509aa..2773023fafe7 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") {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]