Skip to content

Commit 5ca8c55

Browse files
bombsimonayman-sigma
authored andcommitted
Extend exception handling (apache#1884)
1 parent 686f8ed commit 5ca8c55

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
@@ -3019,6 +3019,36 @@ impl From<Set> for Statement {
30193019
}
30203020
}
30213021

3022+
/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute
3023+
/// for the arm.
3024+
///
3025+
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
3026+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
3027+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3028+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3029+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3030+
pub struct ExceptionWhen {
3031+
pub idents: Vec<Ident>,
3032+
pub statements: Vec<Statement>,
3033+
}
3034+
3035+
impl Display for ExceptionWhen {
3036+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3037+
write!(
3038+
f,
3039+
"WHEN {idents} THEN",
3040+
idents = display_separated(&self.idents, " OR ")
3041+
)?;
3042+
3043+
if !self.statements.is_empty() {
3044+
write!(f, " ")?;
3045+
format_statement_list(f, &self.statements)?;
3046+
}
3047+
3048+
Ok(())
3049+
}
3050+
}
3051+
30223052
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
30233053
#[allow(clippy::large_enum_variant)]
30243054
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -3707,17 +3737,20 @@ pub enum Statement {
37073737
/// END;
37083738
/// ```
37093739
statements: Vec<Statement>,
3710-
/// Statements of an exception clause.
3740+
/// Exception handling with exception clauses.
37113741
/// Example:
37123742
/// ```sql
3713-
/// BEGIN
3714-
/// SELECT 1;
3715-
/// EXCEPTION WHEN ERROR THEN
3716-
/// SELECT 2;
3717-
/// SELECT 3;
3718-
/// END;
3743+
/// EXCEPTION
3744+
/// WHEN EXCEPTION_1 THEN
3745+
/// SELECT 2;
3746+
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
3747+
/// SELECT 3;
3748+
/// WHEN OTHER THEN
3749+
/// SELECT 4;
3750+
/// ```
37193751
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
3720-
exception_statements: Option<Vec<Statement>>,
3752+
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
3753+
exception: Option<Vec<ExceptionWhen>>,
37213754
/// TRUE if the statement has an `END` keyword.
37223755
has_end_keyword: bool,
37233756
},
@@ -5562,7 +5595,7 @@ impl fmt::Display for Statement {
55625595
transaction,
55635596
modifier,
55645597
statements,
5565-
exception_statements,
5598+
exception,
55665599
has_end_keyword,
55675600
} => {
55685601
if *syntax_begin {
@@ -5584,11 +5617,10 @@ impl fmt::Display for Statement {
55845617
write!(f, " ")?;
55855618
format_statement_list(f, statements)?;
55865619
}
5587-
if let Some(exception_statements) = exception_statements {
5588-
write!(f, " EXCEPTION WHEN ERROR THEN")?;
5589-
if !exception_statements.is_empty() {
5590-
write!(f, " ")?;
5591-
format_statement_list(f, exception_statements)?;
5620+
if let Some(exception_when) = exception {
5621+
write!(f, " EXCEPTION")?;
5622+
for when in exception_when {
5623+
write!(f, " {when}")?;
55925624
}
55935625
}
55945626
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
@@ -15124,7 +15124,7 @@ impl<'a> Parser<'a> {
1512415124
transaction: Some(BeginTransactionKind::Transaction),
1512515125
modifier: None,
1512615126
statements: vec![],
15127-
exception_statements: None,
15127+
exception: None,
1512815128
has_end_keyword: false,
1512915129
})
1513015130
}
@@ -15156,11 +15156,56 @@ impl<'a> Parser<'a> {
1515615156
transaction,
1515715157
modifier,
1515815158
statements: vec![],
15159-
exception_statements: None,
15159+
exception: None,
1516015160
has_end_keyword: false,
1516115161
})
1516215162
}
1516315163

15164+
pub fn parse_begin_exception_end(&mut self) -> Result<Statement, ParserError> {
15165+
let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
15166+
15167+
let exception = if self.parse_keyword(Keyword::EXCEPTION) {
15168+
let mut when = Vec::new();
15169+
15170+
// We can have multiple `WHEN` arms so we consume all cases until `END`
15171+
while !self.peek_keyword(Keyword::END) {
15172+
self.expect_keyword(Keyword::WHEN)?;
15173+
15174+
// Each `WHEN` case can have one or more conditions, e.g.
15175+
// WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN
15176+
// So we parse identifiers until the `THEN` keyword.
15177+
let mut idents = Vec::new();
15178+
15179+
while !self.parse_keyword(Keyword::THEN) {
15180+
let ident = self.parse_identifier()?;
15181+
idents.push(ident);
15182+
15183+
self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?;
15184+
}
15185+
15186+
let statements = self.parse_statement_list(&[Keyword::WHEN, Keyword::END])?;
15187+
15188+
when.push(ExceptionWhen { idents, statements });
15189+
}
15190+
15191+
Some(when)
15192+
} else {
15193+
None
15194+
};
15195+
15196+
self.expect_keyword(Keyword::END)?;
15197+
15198+
Ok(Statement::StartTransaction {
15199+
begin: true,
15200+
statements,
15201+
exception,
15202+
has_end_keyword: true,
15203+
transaction: None,
15204+
modifier: None,
15205+
modes: Default::default(),
15206+
})
15207+
}
15208+
1516415209
pub fn parse_end(&mut self) -> Result<Statement, ParserError> {
1516515210
let modifier = if !self.dialect.supports_end_transaction_modifier() {
1516615211
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
@@ -8593,8 +8593,11 @@ fn lateral_function() {
85938593
#[test]
85948594
fn parse_start_transaction() {
85958595
let dialects = all_dialects_except(|d|
8596-
// BigQuery does not support this syntax
8597-
d.is::<BigQueryDialect>());
8596+
// BigQuery and Snowflake does not support this syntax
8597+
//
8598+
// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#begin_transaction>
8599+
// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/begin>
8600+
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
85988601
match dialects
85998602
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
86008603
{

tests/sqlparser_snowflake.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4101,3 +4101,67 @@ fn parse_connect_by_root_operator() {
41014101
"sql parser error: Expected an expression, found: FROM"
41024102
);
41034103
}
4104+
4105+
#[test]
4106+
fn test_begin_exception_end() {
4107+
for sql in [
4108+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END",
4109+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END",
4110+
"BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END",
4111+
"BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END",
4112+
] {
4113+
snowflake().verified_stmt(sql);
4114+
}
4115+
4116+
let sql = r#"
4117+
DECLARE
4118+
EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.');
4119+
EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!');
4120+
EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...');
4121+
BEGIN
4122+
BEGIN
4123+
SELECT 1;
4124+
EXCEPTION
4125+
WHEN EXCEPTION_1 THEN
4126+
SELECT 1;
4127+
WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
4128+
SELECT 2;
4129+
SELECT 3;
4130+
WHEN OTHER THEN
4131+
SELECT 4;
4132+
RAISE;
4133+
END;
4134+
END
4135+
"#;
4136+
4137+
// Outer `BEGIN` of the two nested `BEGIN` statements.
4138+
let Statement::StartTransaction { mut statements, .. } = snowflake()
4139+
.parse_sql_statements(sql)
4140+
.unwrap()
4141+
.pop()
4142+
.unwrap()
4143+
else {
4144+
unreachable!();
4145+
};
4146+
4147+
// Inner `BEGIN` of the two nested `BEGIN` statements.
4148+
let Statement::StartTransaction {
4149+
statements,
4150+
exception,
4151+
has_end_keyword,
4152+
..
4153+
} = statements.pop().unwrap()
4154+
else {
4155+
unreachable!();
4156+
};
4157+
4158+
assert_eq!(1, statements.len());
4159+
assert!(has_end_keyword);
4160+
4161+
let exception = exception.unwrap();
4162+
assert_eq!(3, exception.len());
4163+
assert_eq!(1, exception[0].idents.len());
4164+
assert_eq!(1, exception[0].statements.len());
4165+
assert_eq!(2, exception[1].idents.len());
4166+
assert_eq!(2, exception[1].statements.len());
4167+
}

0 commit comments

Comments
 (0)