Skip to content

Commit 0f163a5

Browse files
miland-dbcloud-fan
authored andcommitted
[SPARK-48353][SQL] Introduction of Exception Handling mechanism in SQL Scripting
### What changes were proposed in this pull request? This pull request introduces the logic of error handling inside SQL Scripting language. Now, it is possible to: - declare conditions for specific SQL States (currently only valid in scope where they are defined) - declare handlers to catch and process errors that are risen during statement execution Rules for selecting the most appropriate handler: - Named condition handlers are most specific. - SQLSTATE handlers are next in specificity. - Generic NOT FOUND and SQLEXCEPTION handlers are least specific. Note: Handlers defined in the innermost compound statement where the exception was raised are considered. ### Why are the changes needed? The intent is to add the possibility for user to handle SQL errors in a custom defined way. ### Limitations - Currently, only `EXIT` handler is supported. Support for `CONTINUE` handlers will come in the future. - It is only possible to declare condition in its full form by specifying SQLSTATE. Short form with default SQLSTATE is not yet supported. ### Does this PR introduce any user-facing change? No. ### How was this patch tested? There are already existing test suites for SQL scripting that have been improved to test new functionalities: - `SqlScriptingParserSuite` - `SqlScriptingExecutionSuite` - `SqlScriptingE2eSuite` ### Was this patch authored or co-authored using generative AI tooling? No. Closes #49427 from miland-db/milan-dankovic_data/refactor-execution-4-condition-handlers. Authored-by: Milan Dankovic <[email protected]> Signed-off-by: Wenchen Fan <[email protected]>
1 parent 9d6fb58 commit 0f163a5

File tree

22 files changed

+2069
-99
lines changed

22 files changed

+2069
-99
lines changed

common/utils/src/main/resources/error/error-conditions.json

+91
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,30 @@
12541254
],
12551255
"sqlState" : "42614"
12561256
},
1257+
"DUPLICATE_CONDITION_IN_SCOPE" : {
1258+
"message" : [
1259+
"Found duplicate condition <condition> in the scope. Please, remove one of them."
1260+
],
1261+
"sqlState" : "42734"
1262+
},
1263+
"DUPLICATE_EXCEPTION_HANDLER" : {
1264+
"message" : [
1265+
"Found duplicate handlers. Please, remove one of them."
1266+
],
1267+
"subClass" : {
1268+
"CONDITION" : {
1269+
"message" : [
1270+
"Found duplicate handlers for the same condition <condition>."
1271+
]
1272+
},
1273+
"SQLSTATE" : {
1274+
"message" : [
1275+
"Found duplicate handlers for the same SQLSTATE <sqlState>."
1276+
]
1277+
}
1278+
},
1279+
"sqlState" : "42734"
1280+
},
12571281
"DUPLICATE_KEY" : {
12581282
"message" : [
12591283
"Found duplicate keys <keyColumn>."
@@ -2440,6 +2464,29 @@
24402464
],
24412465
"sqlState" : "42K05"
24422466
},
2467+
"INVALID_ERROR_CONDITION_DECLARATION" : {
2468+
"message" : [
2469+
"Invalid condition declaration."
2470+
],
2471+
"subClass" : {
2472+
"ONLY_AT_BEGINNING" : {
2473+
"message" : [
2474+
"Condition <conditionName> can only be declared at the beginning of the compound."
2475+
]
2476+
},
2477+
"QUALIFIED_CONDITION_NAME" : {
2478+
"message" : [
2479+
"Condition <conditionName> cannot be qualified."
2480+
]
2481+
},
2482+
"SPECIAL_CHARACTER_FOUND" : {
2483+
"message" : [
2484+
"Special character found in condition name <conditionName>. Only alphanumeric characters and underscores are allowed."
2485+
]
2486+
}
2487+
},
2488+
"sqlState" : "42K0R"
2489+
},
24432490
"INVALID_ESC" : {
24442491
"message" : [
24452492
"Found an invalid escape string: <invalidEscape>. The escape string must contain only one character."
@@ -2608,6 +2655,39 @@
26082655
},
26092656
"sqlState" : "HY000"
26102657
},
2658+
"INVALID_HANDLER_DECLARATION" : {
2659+
"message" : [
2660+
"Invalid handler declaration."
2661+
],
2662+
"subClass" : {
2663+
"CONDITION_NOT_FOUND" : {
2664+
"message" : [
2665+
"Condition <condition> not found."
2666+
]
2667+
},
2668+
"DUPLICATE_CONDITION_IN_HANDLER_DECLARATION" : {
2669+
"message" : [
2670+
"Found duplicate condition <condition> in the handler declaration. Please, remove one of them."
2671+
]
2672+
},
2673+
"DUPLICATE_SQLSTATE_IN_HANDLER_DECLARATION" : {
2674+
"message" : [
2675+
"Found duplicate sqlState <sqlState> in the handler declaration. Please, remove one of them."
2676+
]
2677+
},
2678+
"INVALID_CONDITION_COMBINATION" : {
2679+
"message" : [
2680+
"Invalid combination of conditions in the handler declaration. SQLEXCEPTION and NOT FOUND cannot be used together with other condition/sqlstate values."
2681+
]
2682+
},
2683+
"WRONG_PLACE_OF_DECLARATION" : {
2684+
"message" : [
2685+
"Handlers must be declared after variable/condition declaration, and before other statements."
2686+
]
2687+
}
2688+
},
2689+
"sqlState" : "42K0Q"
2690+
},
26112691
"INVALID_IDENTIFIER" : {
26122692
"message" : [
26132693
"The unquoted identifier <ident> is invalid and must be back quoted as: `<ident>`.",
@@ -3264,6 +3344,12 @@
32643344
},
32653345
"sqlState" : "42616"
32663346
},
3347+
"INVALID_SQLSTATE" : {
3348+
"message" : [
3349+
"Invalid SQLSTATE value: '<sqlState>'. SQLSTATE must be exactly 5 characters long and contain only A-Z and 0-9. SQLSTATE must not start with '00', '01', or 'XX'."
3350+
],
3351+
"sqlState" : "428B3"
3352+
},
32673353
"INVALID_SQL_ARG" : {
32683354
"message" : [
32693355
"The argument <name> of `sql()` is invalid. Consider to replace it either by a SQL literal or by collection constructor functions such as `map()`, `array()`, `struct()`."
@@ -5492,6 +5578,11 @@
54925578
"Attach a comment to the namespace <namespace>."
54935579
]
54945580
},
5581+
"CONTINUE_EXCEPTION_HANDLER" : {
5582+
"message" : [
5583+
"CONTINUE exception handler is not supported. Use EXIT handler."
5584+
]
5585+
},
54955586
"DESC_TABLE_COLUMN_JSON" : {
54965587
"message" : [
54975588
"DESC TABLE COLUMN AS JSON not supported for individual columns."

common/utils/src/main/resources/error/error-states.json

+12
Original file line numberDiff line numberDiff line change
@@ -4643,6 +4643,18 @@
46434643
"standard": "N",
46444644
"usedBy": ["Spark"]
46454645
},
4646+
"42K0Q": {
4647+
"description": "Invalid handler declaration.",
4648+
"origin": "Spark",
4649+
"standard": "N",
4650+
"usedBy": ["Spark"]
4651+
},
4652+
"42K0R": {
4653+
"description": "Invalid condition declaration.",
4654+
"origin": "Spark",
4655+
"standard": "N",
4656+
"usedBy": ["Spark"]
4657+
},
46464658
"42KD0": {
46474659
"description": "Ambiguous name reference.",
46484660
"origin": "Databricks",

docs/sql-ref-ansi-compliance.md

+8
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,10 @@ Below is a list of all the keywords in Spark SQL.
459459
|COMPENSATION|non-reserved|non-reserved|non-reserved|
460460
|COMPUTE|non-reserved|non-reserved|non-reserved|
461461
|CONCATENATE|non-reserved|non-reserved|non-reserved|
462+
|CONDITION|non-reserved|non-reserved|non-reserved|
462463
|CONSTRAINT|reserved|non-reserved|reserved|
463464
|CONTAINS|non-reserved|non-reserved|non-reserved|
465+
|CONTINUE|non-reserved|non-reserved|non-reserved|
464466
|COST|non-reserved|non-reserved|non-reserved|
465467
|CREATE|reserved|non-reserved|reserved|
466468
|CROSS|reserved|strict-non-reserved|reserved|
@@ -513,6 +515,7 @@ Below is a list of all the keywords in Spark SQL.
513515
|EXCLUDE|non-reserved|non-reserved|non-reserved|
514516
|EXECUTE|reserved|non-reserved|reserved|
515517
|EXISTS|non-reserved|non-reserved|reserved|
518+
|EXIT|non-reserved|non-reserved|non-reserved|
516519
|EXPLAIN|non-reserved|non-reserved|non-reserved|
517520
|EXPORT|non-reserved|non-reserved|non-reserved|
518521
|EXTEND|non-reserved|non-reserved|non-reserved|
@@ -531,6 +534,7 @@ Below is a list of all the keywords in Spark SQL.
531534
|FOREIGN|reserved|non-reserved|reserved|
532535
|FORMAT|non-reserved|non-reserved|non-reserved|
533536
|FORMATTED|non-reserved|non-reserved|non-reserved|
537+
|FOUND|non-reserved|non-reserved|non-reserved|
534538
|FROM|reserved|non-reserved|reserved|
535539
|FULL|reserved|strict-non-reserved|reserved|
536540
|FUNCTION|non-reserved|non-reserved|reserved|
@@ -540,6 +544,7 @@ Below is a list of all the keywords in Spark SQL.
540544
|GRANT|reserved|non-reserved|reserved|
541545
|GROUP|reserved|non-reserved|reserved|
542546
|GROUPING|non-reserved|non-reserved|reserved|
547+
|HANDLER|non-reserved|non-reserved|non-reserved|
543548
|HAVING|reserved|non-reserved|reserved|
544549
|HOUR|non-reserved|non-reserved|non-reserved|
545550
|HOURS|non-reserved|non-reserved|non-reserved|
@@ -701,6 +706,8 @@ Below is a list of all the keywords in Spark SQL.
701706
|SOURCE|non-reserved|non-reserved|non-reserved|
702707
|SPECIFIC|non-reserved|non-reserved|reserved|
703708
|SQL|reserved|non-reserved|reserved|
709+
|SQLEXCEPTION|non-reserved|non-reserved|non-reserved|
710+
|SQLSTATE|non-reserved|non-reserved|non-reserved|
704711
|START|non-reserved|non-reserved|reserved|
705712
|STATISTICS|non-reserved|non-reserved|non-reserved|
706713
|STORED|non-reserved|non-reserved|non-reserved|
@@ -754,6 +761,7 @@ Below is a list of all the keywords in Spark SQL.
754761
|USE|non-reserved|non-reserved|non-reserved|
755762
|USER|reserved|non-reserved|reserved|
756763
|USING|reserved|strict-non-reserved|reserved|
764+
|VALUE|non-reserved|non-reserved|non-reserved|
757765
|VALUES|non-reserved|non-reserved|reserved|
758766
|VARCHAR|non-reserved|non-reserved|reserved|
759767
|VAR|non-reserved|non-reserved|non-reserved|

sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4

+8
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ COMPACTIONS: 'COMPACTIONS';
174174
COMPENSATION: 'COMPENSATION';
175175
COMPUTE: 'COMPUTE';
176176
CONCATENATE: 'CONCATENATE';
177+
CONDITION: 'CONDITION';
177178
CONSTRAINT: 'CONSTRAINT';
178179
CONTAINS: 'CONTAINS';
180+
CONTINUE: 'CONTINUE';
179181
COST: 'COST';
180182
CREATE: 'CREATE';
181183
CROSS: 'CROSS';
@@ -227,6 +229,7 @@ EXCEPT: 'EXCEPT';
227229
EXCHANGE: 'EXCHANGE';
228230
EXCLUDE: 'EXCLUDE';
229231
EXISTS: 'EXISTS';
232+
EXIT: 'EXIT';
230233
EXPLAIN: 'EXPLAIN';
231234
EXPORT: 'EXPORT';
232235
EXTEND: 'EXTEND';
@@ -245,6 +248,7 @@ FOR: 'FOR';
245248
FOREIGN: 'FOREIGN';
246249
FORMAT: 'FORMAT';
247250
FORMATTED: 'FORMATTED';
251+
FOUND: 'FOUND';
248252
FROM: 'FROM';
249253
FULL: 'FULL';
250254
FUNCTION: 'FUNCTION';
@@ -254,6 +258,7 @@ GLOBAL: 'GLOBAL';
254258
GRANT: 'GRANT';
255259
GROUP: 'GROUP';
256260
GROUPING: 'GROUPING';
261+
HANDLER: 'HANDLER';
257262
HAVING: 'HAVING';
258263
BINARY_HEX: 'X';
259264
HOUR: 'HOUR';
@@ -415,6 +420,8 @@ SORTED: 'SORTED';
415420
SOURCE: 'SOURCE';
416421
SPECIFIC: 'SPECIFIC';
417422
SQL: 'SQL';
423+
SQLEXCEPTION: 'SQLEXCEPTION';
424+
SQLSTATE: 'SQLSTATE';
418425
START: 'START';
419426
STATISTICS: 'STATISTICS';
420427
STORED: 'STORED';
@@ -468,6 +475,7 @@ UPDATE: 'UPDATE';
468475
USE: 'USE';
469476
USER: 'USER';
470477
USING: 'USING';
478+
VALUE: 'VALUE';
471479
VALUES: 'VALUES';
472480
VARCHAR: 'VARCHAR';
473481
VAR: 'VAR';

sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4

+41
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ compoundStatement
6363
: statement
6464
| setStatementWithOptionalVarKeyword
6565
| beginEndCompoundBlock
66+
| declareConditionStatement
67+
| declareHandlerStatement
6668
| ifElseStatement
6769
| caseStatement
6870
| whileStatement
@@ -79,6 +81,29 @@ setStatementWithOptionalVarKeyword
7981
LEFT_PAREN query RIGHT_PAREN #setVariableWithOptionalKeyword
8082
;
8183

84+
sqlStateValue
85+
: stringLit
86+
;
87+
88+
declareConditionStatement
89+
: DECLARE multipartIdentifier CONDITION (FOR SQLSTATE VALUE? sqlStateValue)?
90+
;
91+
92+
conditionValue
93+
: SQLSTATE VALUE? sqlStateValue
94+
| SQLEXCEPTION
95+
| NOT FOUND
96+
| multipartIdentifier
97+
;
98+
99+
conditionValues
100+
: cvList+=conditionValue (COMMA cvList+=conditionValue)*
101+
;
102+
103+
declareHandlerStatement
104+
: DECLARE (CONTINUE | EXIT) HANDLER FOR conditionValues (beginEndCompoundBlock | statement | setStatementWithOptionalVarKeyword)
105+
;
106+
82107
whileStatement
83108
: beginLabel? WHILE booleanExpression DO compoundBody END WHILE endLabel?
84109
;
@@ -1607,7 +1632,9 @@ ansiNonReserved
16071632
| COMPENSATION
16081633
| COMPUTE
16091634
| CONCATENATE
1635+
| CONDITION
16101636
| CONTAINS
1637+
| CONTINUE
16111638
| COST
16121639
| CUBE
16131640
| CURRENT
@@ -1648,6 +1675,7 @@ ansiNonReserved
16481675
| EXCHANGE
16491676
| EXCLUDE
16501677
| EXISTS
1678+
| EXIT
16511679
| EXPLAIN
16521680
| EXPORT
16531681
| EXTEND
@@ -1661,11 +1689,13 @@ ansiNonReserved
16611689
| FOLLOWING
16621690
| FORMAT
16631691
| FORMATTED
1692+
| FOUND
16641693
| FUNCTION
16651694
| FUNCTIONS
16661695
| GENERATED
16671696
| GLOBAL
16681697
| GROUPING
1698+
| HANDLER
16691699
| HOUR
16701700
| HOURS
16711701
| IDENTIFIER_KW
@@ -1798,6 +1828,8 @@ ansiNonReserved
17981828
| SORTED
17991829
| SOURCE
18001830
| SPECIFIC
1831+
| SQLEXCEPTION
1832+
| SQLSTATE
18011833
| START
18021834
| STATISTICS
18031835
| STORED
@@ -1840,6 +1872,7 @@ ansiNonReserved
18401872
| UNTIL
18411873
| UPDATE
18421874
| USE
1875+
| VALUE
18431876
| VALUES
18441877
| VARCHAR
18451878
| VAR
@@ -1945,8 +1978,10 @@ nonReserved
19451978
| COMPENSATION
19461979
| COMPUTE
19471980
| CONCATENATE
1981+
| CONDITION
19481982
| CONSTRAINT
19491983
| CONTAINS
1984+
| CONTINUE
19501985
| COST
19511986
| CREATE
19521987
| CUBE
@@ -1997,6 +2032,7 @@ nonReserved
19972032
| EXCLUDE
19982033
| EXECUTE
19992034
| EXISTS
2035+
| EXIT
20002036
| EXPLAIN
20012037
| EXPORT
20022038
| EXTEND
@@ -2016,13 +2052,15 @@ nonReserved
20162052
| FORMAT
20172053
| FORMATTED
20182054
| FROM
2055+
| FOUND
20192056
| FUNCTION
20202057
| FUNCTIONS
20212058
| GENERATED
20222059
| GLOBAL
20232060
| GRANT
20242061
| GROUP
20252062
| GROUPING
2063+
| HANDLER
20262064
| HAVING
20272065
| HOUR
20282066
| HOURS
@@ -2174,6 +2212,8 @@ nonReserved
21742212
| SOURCE
21752213
| SPECIFIC
21762214
| SQL
2215+
| SQLEXCEPTION
2216+
| SQLSTATE
21772217
| START
21782218
| STATISTICS
21792219
| STORED
@@ -2224,6 +2264,7 @@ nonReserved
22242264
| UPDATE
22252265
| USE
22262266
| USER
2267+
| VALUE
22272268
| VALUES
22282269
| VARCHAR
22292270
| VAR

0 commit comments

Comments
 (0)