Skip to content

Commit 204d3b4

Browse files
authored
Extend exception handling (apache#1884)
1 parent 185a490 commit 204d3b4

File tree

8 files changed

+178
-67
lines changed

8 files changed

+178
-67
lines changed

src/ast/mod.rs

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,36 @@ impl From<Set> for Statement {
29902990
}
29912991
}
29922992

2993+
/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute
2994+
/// for the arm.
2995+
///
2996+
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
2997+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
2998+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2999+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3000+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3001+
pub struct ExceptionWhen {
3002+
pub idents: Vec<Ident>,
3003+
pub statements: Vec<Statement>,
3004+
}
3005+
3006+
impl Display for ExceptionWhen {
3007+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3008+
write!(
3009+
f,
3010+
"WHEN {idents} THEN",
3011+
idents = display_separated(&self.idents, " OR ")
3012+
)?;
3013+
3014+
if !self.statements.is_empty() {
3015+
write!(f, " ")?;
3016+
format_statement_list(f, &self.statements)?;
3017+
}
3018+
3019+
Ok(())
3020+
}
3021+
}
3022+
29933023
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
29943024
#[allow(clippy::large_enum_variant)]
29953025
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -3678,17 +3708,20 @@ pub enum Statement {
36783708
/// END;
36793709
/// ```
36803710
statements: Vec<Statement>,
3681-
/// Statements of an exception clause.
3711+
/// Exception handling with exception clauses.
36823712
/// Example:
36833713
/// ```sql
3684-
/// BEGIN
3685-
/// SELECT 1;
3686-
/// EXCEPTION WHEN ERROR THEN
3687-
/// SELECT 2;
3688-
/// SELECT 3;
3689-
/// END;
3714+
/// EXCEPTION
3715+
/// WHEN EXCEPTION_1 THEN
3716+
/// SELECT 2;
3717+
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
3718+
/// SELECT 3;
3719+
/// WHEN OTHER THEN
3720+
/// SELECT 4;
3721+
/// ```
36903722
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
3691-
exception_statements: Option<Vec<Statement>>,
3723+
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
3724+
exception: Option<Vec<ExceptionWhen>>,
36923725
/// TRUE if the statement has an `END` keyword.
36933726
has_end_keyword: bool,
36943727
},
@@ -5533,7 +5566,7 @@ impl fmt::Display for Statement {
55335566
transaction,
55345567
modifier,
55355568
statements,
5536-
exception_statements,
5569+
exception,
55375570
has_end_keyword,
55385571
} => {
55395572
if *syntax_begin {
@@ -5555,11 +5588,10 @@ impl fmt::Display for Statement {
55555588
write!(f, " ")?;
55565589
format_statement_list(f, statements)?;
55575590
}
5558-
if let Some(exception_statements) = exception_statements {
5559-
write!(f, " EXCEPTION WHEN ERROR THEN")?;
5560-
if !exception_statements.is_empty() {
5561-
write!(f, " ")?;
5562-
format_statement_list(f, exception_statements)?;
5591+
if let Some(exception_when) = exception {
5592+
write!(f, " EXCEPTION")?;
5593+
for when in exception_when {
5594+
write!(f, " {when}")?;
55635595
}
55645596
}
55655597
if *has_end_keyword {

src/dialect/bigquery.rs

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ pub struct BigQueryDialect;
4646

4747
impl Dialect for BigQueryDialect {
4848
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
49-
self.maybe_parse_statement(parser)
49+
if parser.parse_keyword(Keyword::BEGIN) {
50+
return Some(parser.parse_begin_exception_end());
51+
}
52+
53+
None
5054
}
5155

5256
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
@@ -141,48 +145,3 @@ impl Dialect for BigQueryDialect {
141145
true
142146
}
143147
}
144-
145-
impl BigQueryDialect {
146-
fn maybe_parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
147-
if parser.peek_keyword(Keyword::BEGIN) {
148-
return Some(self.parse_begin(parser));
149-
}
150-
None
151-
}
152-
153-
/// Parse a `BEGIN` statement.
154-
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
155-
fn parse_begin(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
156-
parser.expect_keyword(Keyword::BEGIN)?;
157-
158-
let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
159-
160-
let has_exception_when_clause = parser.parse_keywords(&[
161-
Keyword::EXCEPTION,
162-
Keyword::WHEN,
163-
Keyword::ERROR,
164-
Keyword::THEN,
165-
]);
166-
let exception_statements = if has_exception_when_clause {
167-
if !parser.peek_keyword(Keyword::END) {
168-
Some(parser.parse_statement_list(&[Keyword::END])?)
169-
} else {
170-
Some(Default::default())
171-
}
172-
} else {
173-
None
174-
};
175-
176-
parser.expect_keyword(Keyword::END)?;
177-
178-
Ok(Statement::StartTransaction {
179-
begin: true,
180-
statements,
181-
exception_statements,
182-
has_end_keyword: true,
183-
transaction: None,
184-
modifier: None,
185-
modes: Default::default(),
186-
})
187-
}
188-
}

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ impl Dialect for SnowflakeDialect {
131131
}
132132

133133
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
134+
if parser.parse_keyword(Keyword::BEGIN) {
135+
return Some(parser.parse_begin_exception_end());
136+
}
137+
134138
if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
135139
// ALTER SESSION
136140
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ define_keywords!(
646646
ORDER,
647647
ORDINALITY,
648648
ORGANIZATION,
649+
OTHER,
649650
OUT,
650651
OUTER,
651652
OUTPUT,

src/parser/mod.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15092,7 +15092,7 @@ impl<'a> Parser<'a> {
1509215092
transaction: Some(BeginTransactionKind::Transaction),
1509315093
modifier: None,
1509415094
statements: vec![],
15095-
exception_statements: None,
15095+
exception: None,
1509615096
has_end_keyword: false,
1509715097
})
1509815098
}
@@ -15124,11 +15124,56 @@ impl<'a> Parser<'a> {
1512415124
transaction,
1512515125
modifier,
1512615126
statements: vec![],
15127-
exception_statements: None,
15127+
exception: None,
1512815128
has_end_keyword: false,
1512915129
})
1513015130
}
1513115131

15132+
pub fn parse_begin_exception_end(&mut self) -> Result<Statement, ParserError> {
15133+
let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
15134+
15135+
let exception = if self.parse_keyword(Keyword::EXCEPTION) {
15136+
let mut when = Vec::new();
15137+
15138+
// We can have multiple `WHEN` arms so we consume all cases until `END`
15139+
while !self.peek_keyword(Keyword::END) {
15140+
self.expect_keyword(Keyword::WHEN)?;
15141+
15142+
// Each `WHEN` case can have one or more conditions, e.g.
15143+
// WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN
15144+
// So we parse identifiers until the `THEN` keyword.
15145+
let mut idents = Vec::new();
15146+
15147+
while !self.parse_keyword(Keyword::THEN) {
15148+
let ident = self.parse_identifier()?;
15149+
idents.push(ident);
15150+
15151+
self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?;
15152+
}
15153+
15154+
let statements = self.parse_statement_list(&[Keyword::WHEN, Keyword::END])?;
15155+
15156+
when.push(ExceptionWhen { idents, statements });
15157+
}
15158+
15159+
Some(when)
15160+
} else {
15161+
None
15162+
};
15163+
15164+
self.expect_keyword(Keyword::END)?;
15165+
15166+
Ok(Statement::StartTransaction {
15167+
begin: true,
15168+
statements,
15169+
exception,
15170+
has_end_keyword: true,
15171+
transaction: None,
15172+
modifier: None,
15173+
modes: Default::default(),
15174+
})
15175+
}
15176+
1513215177
pub fn parse_end(&mut self) -> Result<Statement, ParserError> {
1513315178
let modifier = if !self.dialect.supports_end_transaction_modifier() {
1513415179
None

tests/sqlparser_bigquery.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,21 @@ fn parse_at_at_identifier() {
261261

262262
#[test]
263263
fn parse_begin() {
264-
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#;
264+
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; RAISE USING MESSAGE = FORMAT('ERR: %s', 'Bad'); END"#;
265265
let Statement::StartTransaction {
266266
statements,
267-
exception_statements,
267+
exception,
268268
has_end_keyword,
269269
..
270270
} = bigquery().verified_stmt(sql)
271271
else {
272272
unreachable!();
273273
};
274274
assert_eq!(1, statements.len());
275-
assert_eq!(1, exception_statements.unwrap().len());
275+
assert!(exception.is_some());
276+
277+
let exception = exception.unwrap();
278+
assert_eq!(1, exception.len());
276279
assert!(has_end_keyword);
277280

278281
bigquery().verified_stmt(

tests/sqlparser_common.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8592,8 +8592,11 @@ fn lateral_function() {
85928592
#[test]
85938593
fn parse_start_transaction() {
85948594
let dialects = all_dialects_except(|d|
8595-
// BigQuery does not support this syntax
8596-
d.is::<BigQueryDialect>());
8595+
// BigQuery and Snowflake does not support this syntax
8596+
//
8597+
// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#begin_transaction>
8598+
// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/begin>
8599+
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
85978600
match dialects
85988601
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
85998602
{

tests/sqlparser_snowflake.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,3 +4082,67 @@ fn parse_connect_by_root_operator() {
40824082
"sql parser error: Expected an expression, found: FROM"
40834083
);
40844084
}
4085+
4086+
#[test]
4087+
fn test_begin_exception_end() {
4088+
for sql in [
4089+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END",
4090+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END",
4091+
"BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END",
4092+
"BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END",
4093+
] {
4094+
snowflake().verified_stmt(sql);
4095+
}
4096+
4097+
let sql = r#"
4098+
DECLARE
4099+
EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.');
4100+
EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!');
4101+
EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...');
4102+
BEGIN
4103+
BEGIN
4104+
SELECT 1;
4105+
EXCEPTION
4106+
WHEN EXCEPTION_1 THEN
4107+
SELECT 1;
4108+
WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
4109+
SELECT 2;
4110+
SELECT 3;
4111+
WHEN OTHER THEN
4112+
SELECT 4;
4113+
RAISE;
4114+
END;
4115+
END
4116+
"#;
4117+
4118+
// Outer `BEGIN` of the two nested `BEGIN` statements.
4119+
let Statement::StartTransaction { mut statements, .. } = snowflake()
4120+
.parse_sql_statements(sql)
4121+
.unwrap()
4122+
.pop()
4123+
.unwrap()
4124+
else {
4125+
unreachable!();
4126+
};
4127+
4128+
// Inner `BEGIN` of the two nested `BEGIN` statements.
4129+
let Statement::StartTransaction {
4130+
statements,
4131+
exception,
4132+
has_end_keyword,
4133+
..
4134+
} = statements.pop().unwrap()
4135+
else {
4136+
unreachable!();
4137+
};
4138+
4139+
assert_eq!(1, statements.len());
4140+
assert!(has_end_keyword);
4141+
4142+
let exception = exception.unwrap();
4143+
assert_eq!(3, exception.len());
4144+
assert_eq!(1, exception[0].idents.len());
4145+
assert_eq!(1, exception[0].statements.len());
4146+
assert_eq!(2, exception[1].idents.len());
4147+
assert_eq!(2, exception[1].statements.len());
4148+
}

0 commit comments

Comments
 (0)