Skip to content

Commit 6f5fe30

Browse files
committed
Extend exception handling
- Exception handling can handle multiple `WHEN` arms - Exception can re-raise with `RAISE` keyword - Snowflake can now also parse `BEGIN ... EXCEPTION ... END` Example: ```sql BEGIN SELECT 1; EXCEPTION WHEN EXCEPTION_1 THEN SELECT 1; WHEN EXCEPTION_2 OR EXCEPTION_3 THEN SELECT 2; SELECT 3; WHEN OTHER THEN SELECT 4; RAISE; END; ```
1 parent 703ba2c commit 6f5fe30

File tree

7 files changed

+208
-68
lines changed

7 files changed

+208
-68
lines changed

src/ast/mod.rs

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,6 +2982,63 @@ impl From<Set> for Statement {
29822982
}
29832983
}
29842984

2985+
/// An exception representing exception handling with the `EXCEPT` keyword.
2986+
///
2987+
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
2988+
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
2989+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2990+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2991+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2992+
pub struct Exception {
2993+
pub when: Vec<ExceptionWhen>,
2994+
pub raises: Option<Box<Statement>>,
2995+
}
2996+
2997+
impl Display for Exception {
2998+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2999+
write!(f, " EXCEPTION")?;
3000+
for w in &self.when {
3001+
write!(f, "{w}")?;
3002+
}
3003+
3004+
if let Some(raises) = &self.raises {
3005+
write!(f, " {raises};")?;
3006+
}
3007+
3008+
Ok(())
3009+
}
3010+
}
3011+
3012+
/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute
3013+
/// for the arm.
3014+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3015+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3016+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3017+
pub struct ExceptionWhen {
3018+
pub idents: Vec<Ident>,
3019+
pub statements: Vec<Statement>,
3020+
}
3021+
3022+
impl Display for ExceptionWhen {
3023+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3024+
let idents = self
3025+
.idents
3026+
.iter()
3027+
.map(ToString::to_string)
3028+
.collect::<Vec<_>>()
3029+
.join(" OR ");
3030+
3031+
write!(f, " WHEN {idents} THEN", idents = idents)?;
3032+
3033+
if !self.statements.is_empty() {
3034+
write!(f, " ")?;
3035+
format_statement_list(f, &self.statements)?;
3036+
}
3037+
3038+
Ok(())
3039+
}
3040+
}
3041+
29853042
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
29863043
#[allow(clippy::large_enum_variant)]
29873044
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -3670,17 +3727,24 @@ pub enum Statement {
36703727
/// END;
36713728
/// ```
36723729
statements: Vec<Statement>,
3673-
/// Statements of an exception clause.
3730+
/// Exception handling with exception clauses and raises.
36743731
/// Example:
36753732
/// ```sql
36763733
/// BEGIN
36773734
/// SELECT 1;
3678-
/// EXCEPTION WHEN ERROR THEN
3679-
/// SELECT 2;
3680-
/// SELECT 3;
3735+
/// EXCEPTION
3736+
/// WHEN EXCEPTION_1 THEN
3737+
/// select 2;
3738+
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
3739+
/// select 3;
3740+
/// WHEN OTHER THEN
3741+
/// SELECT 4;
3742+
/// RAISE;
36813743
/// END;
3744+
/// ```
36823745
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
3683-
exception_statements: Option<Vec<Statement>>,
3746+
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
3747+
exception: Option<Exception>,
36843748
/// TRUE if the statement has an `END` keyword.
36853749
has_end_keyword: bool,
36863750
},
@@ -5525,7 +5589,7 @@ impl fmt::Display for Statement {
55255589
transaction,
55265590
modifier,
55275591
statements,
5528-
exception_statements,
5592+
exception,
55295593
has_end_keyword,
55305594
} => {
55315595
if *syntax_begin {
@@ -5547,12 +5611,8 @@ impl fmt::Display for Statement {
55475611
write!(f, " ")?;
55485612
format_statement_list(f, statements)?;
55495613
}
5550-
if let Some(exception_statements) = exception_statements {
5551-
write!(f, " EXCEPTION WHEN ERROR THEN")?;
5552-
if !exception_statements.is_empty() {
5553-
write!(f, " ")?;
5554-
format_statement_list(f, exception_statements)?;
5555-
}
5614+
if let Some(exception) = exception {
5615+
write!(f, "{exception}")?;
55565616
}
55575617
if *has_end_keyword {
55585618
write!(f, " END")?;

src/dialect/bigquery.rs

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::ast::Statement;
1918
use crate::dialect::Dialect;
2019
use crate::keywords::Keyword;
21-
use crate::parser::{Parser, ParserError};
20+
use crate::parser::Parser;
2221

2322
/// These keywords are disallowed as column identifiers. Such that
2423
/// `SELECT 5 AS <col> FROM T` is rejected by BigQuery.
@@ -45,10 +44,6 @@ const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
4544
pub struct BigQueryDialect;
4645

4746
impl Dialect for BigQueryDialect {
48-
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
49-
self.maybe_parse_statement(parser)
50-
}
51-
5247
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
5348
fn is_delimited_identifier_start(&self, ch: char) -> bool {
5449
ch == '`'
@@ -141,48 +136,3 @@ impl Dialect for BigQueryDialect {
141136
true
142137
}
143138
}
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/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: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15096,12 +15096,16 @@ impl<'a> Parser<'a> {
1509615096
transaction: Some(BeginTransactionKind::Transaction),
1509715097
modifier: None,
1509815098
statements: vec![],
15099-
exception_statements: None,
15099+
exception: None,
1510015100
has_end_keyword: false,
1510115101
})
1510215102
}
1510315103

1510415104
pub fn parse_begin(&mut self) -> Result<Statement, ParserError> {
15105+
if dialect_of!(self is SnowflakeDialect | BigQueryDialect) {
15106+
return self.parse_begin_exception_end();
15107+
}
15108+
1510515109
let modifier = if !self.dialect.supports_start_transaction_modifier() {
1510615110
None
1510715111
} else if self.parse_keyword(Keyword::DEFERRED) {
@@ -15128,11 +15132,69 @@ impl<'a> Parser<'a> {
1512815132
transaction,
1512915133
modifier,
1513015134
statements: vec![],
15131-
exception_statements: None,
15135+
exception: None,
1513215136
has_end_keyword: false,
1513315137
})
1513415138
}
1513515139

15140+
pub fn parse_begin_exception_end(&mut self) -> Result<Statement, ParserError> {
15141+
let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
15142+
15143+
let exception = if self.parse_keyword(Keyword::EXCEPTION) {
15144+
let mut when = Vec::new();
15145+
15146+
// We can have multiple `WHEN` arms so we consume all cases until `END` or an exception
15147+
// is `RAISE`ed.
15148+
while self
15149+
.peek_one_of_keywords(&[Keyword::END, Keyword::RAISE])
15150+
.is_none()
15151+
{
15152+
self.expect_keyword(Keyword::WHEN)?;
15153+
15154+
// Each `WHEN` case can have one or more conditions, e.g.
15155+
// WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN
15156+
// So we parse identifiers until the `THEN` keyword.
15157+
let mut idents = Vec::new();
15158+
15159+
while !self.parse_keyword(Keyword::THEN) {
15160+
let ident = self.parse_identifier()?;
15161+
idents.push(ident);
15162+
15163+
self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?;
15164+
}
15165+
15166+
let statements =
15167+
self.parse_statement_list(&[Keyword::WHEN, Keyword::RAISE, Keyword::END])?;
15168+
15169+
when.push(ExceptionWhen { idents, statements });
15170+
}
15171+
15172+
let raises = if self.peek_keyword(Keyword::RAISE) {
15173+
let raises = Some(Box::new(self.parse_raise_stmt()?));
15174+
self.expect_token(&Token::SemiColon)?;
15175+
raises
15176+
} else {
15177+
None
15178+
};
15179+
15180+
Some(Exception { when, raises })
15181+
} else {
15182+
None
15183+
};
15184+
15185+
self.expect_keyword(Keyword::END)?;
15186+
15187+
Ok(Statement::StartTransaction {
15188+
begin: true,
15189+
statements,
15190+
exception,
15191+
has_end_keyword: true,
15192+
transaction: None,
15193+
modifier: None,
15194+
modes: Default::default(),
15195+
})
15196+
}
15197+
1513615198
pub fn parse_end(&mut self) -> Result<Statement, ParserError> {
1513715199
let modifier = if !self.dialect.supports_end_transaction_modifier() {
1513815200
None

tests/sqlparser_bigquery.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,18 @@ fn parse_begin() {
264264
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; 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.when.len());
276279
assert!(has_end_keyword);
277280

278281
bigquery().verified_stmt(

tests/sqlparser_common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8593,7 +8593,7 @@ fn lateral_function() {
85938593
fn parse_start_transaction() {
85948594
let dialects = all_dialects_except(|d|
85958595
// BigQuery does not support this syntax
8596-
d.is::<BigQueryDialect>());
8596+
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
85978597
match dialects
85988598
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
85998599
{

tests/sqlparser_snowflake.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4066,3 +4066,67 @@ fn parse_connect_by_root_operator() {
40664066
"sql parser error: Expected an expression, found: FROM"
40674067
);
40684068
}
4069+
4070+
#[test]
4071+
fn test_begin_exception_end() {
4072+
for sql in [
4073+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END",
4074+
"BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END",
4075+
"BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END",
4076+
"BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END",
4077+
] {
4078+
snowflake().verified_stmt(sql);
4079+
}
4080+
4081+
let sql = r#"
4082+
DECLARE
4083+
EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.');
4084+
EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!');
4085+
EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...');
4086+
BEGIN
4087+
BEGIN
4088+
SELECT 1;
4089+
EXCEPTION
4090+
WHEN EXCEPTION_1 THEN
4091+
SELECT 1;
4092+
WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
4093+
SELECT 2;
4094+
SELECT 3;
4095+
WHEN OTHER THEN
4096+
SELECT 4;
4097+
RAISE;
4098+
END;
4099+
END
4100+
"#;
4101+
4102+
// Outer `BEGIN` of the two nested `BEGIN` statements.
4103+
let Statement::StartTransaction { mut statements, .. } = snowflake()
4104+
.parse_sql_statements(sql)
4105+
.unwrap()
4106+
.pop()
4107+
.unwrap()
4108+
else {
4109+
unreachable!();
4110+
};
4111+
4112+
// Inner `BEGIN` of the two nested `BEGIN` statements.
4113+
let Statement::StartTransaction {
4114+
statements,
4115+
exception,
4116+
has_end_keyword,
4117+
..
4118+
} = statements.pop().unwrap()
4119+
else {
4120+
unreachable!();
4121+
};
4122+
4123+
assert_eq!(1, statements.len());
4124+
assert!(has_end_keyword);
4125+
4126+
let exception = exception.unwrap();
4127+
assert_eq!(3, exception.when.len());
4128+
assert_eq!(1, exception.when[0].idents.len());
4129+
assert_eq!(1, exception.when[0].statements.len());
4130+
assert_eq!(2, exception.when[1].idents.len());
4131+
assert_eq!(2, exception.when[1].statements.len());
4132+
}

0 commit comments

Comments
 (0)