From 61263205c30188eef7e01f64b9e1c1041379f354 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 12 Jun 2025 16:44:44 +0200 Subject: [PATCH 1/7] 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; ``` --- src/ast/mod.rs | 84 ++++++++++++++++++++++++++++++------ src/dialect/bigquery.rs | 52 +--------------------- src/keywords.rs | 1 + src/parser/mod.rs | 66 +++++++++++++++++++++++++++- tests/sqlparser_bigquery.rs | 7 ++- tests/sqlparser_common.rs | 2 +- tests/sqlparser_snowflake.rs | 64 +++++++++++++++++++++++++++ 7 files changed, 208 insertions(+), 68 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a1d8ff6fc..a9625ea5d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2982,6 +2982,63 @@ impl From for Statement { } } +/// An exception representing exception handling with the `EXCEPTION` keyword. +/// +/// Snowflake: +/// BigQuery: +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct Exception { + pub when: Vec, + pub raises: Option>, +} + +impl Display for Exception { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, " EXCEPTION")?; + for w in &self.when { + write!(f, "{w}")?; + } + + if let Some(raises) = &self.raises { + write!(f, " {raises};")?; + } + + Ok(()) + } +} + +/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute +/// for the arm. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ExceptionWhen { + pub idents: Vec, + pub statements: Vec, +} + +impl Display for ExceptionWhen { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let idents = self + .idents + .iter() + .map(ToString::to_string) + .collect::>() + .join(" OR "); + + write!(f, " WHEN {idents} THEN", idents = idents)?; + + if !self.statements.is_empty() { + write!(f, " ")?; + format_statement_list(f, &self.statements)?; + } + + Ok(()) + } +} + /// A top-level statement (SELECT, INSERT, CREATE, etc.) #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -3670,17 +3727,24 @@ pub enum Statement { /// END; /// ``` statements: Vec, - /// Statements of an exception clause. + /// Exception handling with exception clauses and raises. /// Example: /// ```sql /// BEGIN /// SELECT 1; - /// EXCEPTION WHEN ERROR THEN - /// SELECT 2; - /// SELECT 3; + /// EXCEPTION + /// WHEN EXCEPTION_1 THEN + /// select 2; + /// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN + /// select 3; + /// WHEN OTHER THEN + /// SELECT 4; + /// RAISE; /// END; + /// ``` /// - exception_statements: Option>, + /// + exception: Option, /// TRUE if the statement has an `END` keyword. has_end_keyword: bool, }, @@ -5525,7 +5589,7 @@ impl fmt::Display for Statement { transaction, modifier, statements, - exception_statements, + exception, has_end_keyword, } => { if *syntax_begin { @@ -5547,12 +5611,8 @@ impl fmt::Display for Statement { write!(f, " ")?; format_statement_list(f, statements)?; } - if let Some(exception_statements) = exception_statements { - write!(f, " EXCEPTION WHEN ERROR THEN")?; - if !exception_statements.is_empty() { - write!(f, " ")?; - format_statement_list(f, exception_statements)?; - } + if let Some(exception) = exception { + write!(f, "{exception}")?; } if *has_end_keyword { write!(f, " END")?; diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index 68ca1390a..48b0e7d11 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -15,10 +15,9 @@ // specific language governing permissions and limitations // under the License. -use crate::ast::Statement; use crate::dialect::Dialect; use crate::keywords::Keyword; -use crate::parser::{Parser, ParserError}; +use crate::parser::Parser; /// These keywords are disallowed as column identifiers. Such that /// `SELECT 5 AS FROM T` is rejected by BigQuery. @@ -45,10 +44,6 @@ const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[ pub struct BigQueryDialect; impl Dialect for BigQueryDialect { - fn parse_statement(&self, parser: &mut Parser) -> Option> { - self.maybe_parse_statement(parser) - } - /// See fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '`' @@ -141,48 +136,3 @@ impl Dialect for BigQueryDialect { true } } - -impl BigQueryDialect { - fn maybe_parse_statement(&self, parser: &mut Parser) -> Option> { - if parser.peek_keyword(Keyword::BEGIN) { - return Some(self.parse_begin(parser)); - } - None - } - - /// Parse a `BEGIN` statement. - /// - fn parse_begin(&self, parser: &mut Parser) -> Result { - parser.expect_keyword(Keyword::BEGIN)?; - - let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; - - let has_exception_when_clause = parser.parse_keywords(&[ - Keyword::EXCEPTION, - Keyword::WHEN, - Keyword::ERROR, - Keyword::THEN, - ]); - let exception_statements = if has_exception_when_clause { - if !parser.peek_keyword(Keyword::END) { - Some(parser.parse_statement_list(&[Keyword::END])?) - } else { - Some(Default::default()) - } - } else { - None - }; - - parser.expect_keyword(Keyword::END)?; - - Ok(Statement::StartTransaction { - begin: true, - statements, - exception_statements, - has_end_keyword: true, - transaction: None, - modifier: None, - modes: Default::default(), - }) - } -} diff --git a/src/keywords.rs b/src/keywords.rs index f5c5e567e..cb6c7a6e2 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -646,6 +646,7 @@ define_keywords!( ORDER, ORDINALITY, ORGANIZATION, + OTHER, OUT, OUTER, OUTPUT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2c208e2e5..7955b6657 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15096,12 +15096,16 @@ impl<'a> Parser<'a> { transaction: Some(BeginTransactionKind::Transaction), modifier: None, statements: vec![], - exception_statements: None, + exception: None, has_end_keyword: false, }) } pub fn parse_begin(&mut self) -> Result { + if dialect_of!(self is SnowflakeDialect | BigQueryDialect) { + return self.parse_begin_exception_end(); + } + let modifier = if !self.dialect.supports_start_transaction_modifier() { None } else if self.parse_keyword(Keyword::DEFERRED) { @@ -15128,11 +15132,69 @@ impl<'a> Parser<'a> { transaction, modifier, statements: vec![], - exception_statements: None, + exception: None, has_end_keyword: false, }) } + pub fn parse_begin_exception_end(&mut self) -> Result { + let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; + + let exception = if self.parse_keyword(Keyword::EXCEPTION) { + let mut when = Vec::new(); + + // We can have multiple `WHEN` arms so we consume all cases until `END` or an exception + // is `RAISE`ed. + while self + .peek_one_of_keywords(&[Keyword::END, Keyword::RAISE]) + .is_none() + { + self.expect_keyword(Keyword::WHEN)?; + + // Each `WHEN` case can have one or more conditions, e.g. + // WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN + // So we parse identifiers until the `THEN` keyword. + let mut idents = Vec::new(); + + while !self.parse_keyword(Keyword::THEN) { + let ident = self.parse_identifier()?; + idents.push(ident); + + self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?; + } + + let statements = + self.parse_statement_list(&[Keyword::WHEN, Keyword::RAISE, Keyword::END])?; + + when.push(ExceptionWhen { idents, statements }); + } + + let raises = if self.peek_keyword(Keyword::RAISE) { + let raises = Some(Box::new(self.parse_raise_stmt()?)); + self.expect_token(&Token::SemiColon)?; + raises + } else { + None + }; + + Some(Exception { when, raises }) + } else { + None + }; + + self.expect_keyword(Keyword::END)?; + + Ok(Statement::StartTransaction { + begin: true, + statements, + exception, + has_end_keyword: true, + transaction: None, + modifier: None, + modes: Default::default(), + }) + } + pub fn parse_end(&mut self) -> Result { let modifier = if !self.dialect.supports_end_transaction_modifier() { None diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index b64f190f6..87608a651 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -264,7 +264,7 @@ fn parse_begin() { let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#; let Statement::StartTransaction { statements, - exception_statements, + exception, has_end_keyword, .. } = bigquery().verified_stmt(sql) @@ -272,7 +272,10 @@ fn parse_begin() { unreachable!(); }; assert_eq!(1, statements.len()); - assert_eq!(1, exception_statements.unwrap().len()); + assert!(exception.is_some()); + + let exception = exception.unwrap(); + assert_eq!(1, exception.when.len()); assert!(has_end_keyword); bigquery().verified_stmt( diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index abcadb458..e31e7848f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8593,7 +8593,7 @@ fn lateral_function() { fn parse_start_transaction() { let dialects = all_dialects_except(|d| // BigQuery does not support this syntax - d.is::()); + d.is::() || d.is::()); match dialects .verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7a164c248..102aa62c1 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4066,3 +4066,67 @@ fn parse_connect_by_root_operator() { "sql parser error: Expected an expression, found: FROM" ); } + +#[test] +fn test_begin_exception_end() { + for sql in [ + "BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END", + "BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END", + "BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END", + "BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END", + ] { + snowflake().verified_stmt(sql); + } + + let sql = r#" +DECLARE + EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.'); + EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!'); + EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...'); +BEGIN + 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; +END +"#; + + // Outer `BEGIN` of the two nested `BEGIN` statements. + let Statement::StartTransaction { mut statements, .. } = snowflake() + .parse_sql_statements(sql) + .unwrap() + .pop() + .unwrap() + else { + unreachable!(); + }; + + // Inner `BEGIN` of the two nested `BEGIN` statements. + let Statement::StartTransaction { + statements, + exception, + has_end_keyword, + .. + } = statements.pop().unwrap() + else { + unreachable!(); + }; + + assert_eq!(1, statements.len()); + assert!(has_end_keyword); + + let exception = exception.unwrap(); + assert_eq!(3, exception.when.len()); + assert_eq!(1, exception.when[0].idents.len()); + assert_eq!(1, exception.when[0].statements.len()); + assert_eq!(2, exception.when[1].idents.len()); + assert_eq!(2, exception.when[1].statements.len()); +} From f69e0d928c2ee6a72261a3dace68228d7ad79e96 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 13 Jun 2025 08:58:20 +0200 Subject: [PATCH 2/7] Use consistent SQL casing --- src/ast/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a9625ea5d..5e3f4cf45 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3734,9 +3734,9 @@ pub enum Statement { /// SELECT 1; /// EXCEPTION /// WHEN EXCEPTION_1 THEN - /// select 2; + /// SELECT 2; /// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN - /// select 3; + /// SELECT 3; /// WHEN OTHER THEN /// SELECT 4; /// RAISE; From 33979fb8a2f7ce3a8e2bff61168fcc459ddf08a1 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 16 Jun 2025 17:17:46 +0200 Subject: [PATCH 3/7] Add documentation, use `display_separated`, rename `Exception` --- src/ast/mod.rs | 33 ++++++++++++++++++++++----------- src/parser/mod.rs | 2 +- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5e3f4cf45..0c637aad2 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2989,12 +2989,23 @@ impl From for Statement { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct Exception { +pub struct ExceptionClause { + /// When represents each `WHEN` case pub when: Vec, + /// The exception that is being raised or the current exception being handled if `None`. + /// + /// Example + /// RAISE; + /// RAISE MY_EXCEPTION; + /// RAISE USING MESSAGE = "Some error"; + /// + /// BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#raise + /// Snowflake: + // pub raises: Option>, pub raises: Option>, } -impl Display for Exception { +impl Display for ExceptionClause { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, " EXCEPTION")?; for w in &self.when { @@ -3011,6 +3022,9 @@ impl Display for Exception { /// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute /// for the arm. +/// +/// Snowflake: +/// BigQuery: #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -3021,14 +3035,11 @@ pub struct ExceptionWhen { impl Display for ExceptionWhen { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let idents = self - .idents - .iter() - .map(ToString::to_string) - .collect::>() - .join(" OR "); - - write!(f, " WHEN {idents} THEN", idents = idents)?; + write!( + f, + " WHEN {idents} THEN", + idents = display_separated(&self.idents, " OR ") + )?; if !self.statements.is_empty() { write!(f, " ")?; @@ -3744,7 +3755,7 @@ pub enum Statement { /// ``` /// /// - exception: Option, + exception: Option, /// TRUE if the statement has an `END` keyword. has_end_keyword: bool, }, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7955b6657..625ff49b5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15177,7 +15177,7 @@ impl<'a> Parser<'a> { None }; - Some(Exception { when, raises }) + Some(ExceptionClause { when, raises }) } else { None }; From 23350bd8d8ee1ff87e2cacb6c9654a9ba1ab76a4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 16 Jun 2025 17:42:39 +0200 Subject: [PATCH 4/7] Use separate trait impl in favor of `dialect_of!` macro --- src/dialect/bigquery.rs | 11 ++++++++++- src/dialect/snowflake.rs | 4 ++++ src/parser/mod.rs | 4 ---- tests/sqlparser_common.rs | 5 ++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index 48b0e7d11..c2cd507ce 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -15,9 +15,10 @@ // specific language governing permissions and limitations // under the License. +use crate::ast::Statement; use crate::dialect::Dialect; use crate::keywords::Keyword; -use crate::parser::Parser; +use crate::parser::{Parser, ParserError}; /// These keywords are disallowed as column identifiers. Such that /// `SELECT 5 AS FROM T` is rejected by BigQuery. @@ -44,6 +45,14 @@ const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[ pub struct BigQueryDialect; impl Dialect for BigQueryDialect { + fn parse_statement(&self, parser: &mut Parser) -> Option> { + if parser.parse_keyword(Keyword::BEGIN) { + return Some(parser.parse_begin_exception_end()); + } + + None + } + /// See fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '`' diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 990e2ea29..cbd7bcf2e 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -131,6 +131,10 @@ impl Dialect for SnowflakeDialect { } fn parse_statement(&self, parser: &mut Parser) -> Option> { + if parser.parse_keyword(Keyword::BEGIN) { + return Some(parser.parse_begin_exception_end()); + } + if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) { // ALTER SESSION let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 625ff49b5..28faf0b62 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15102,10 +15102,6 @@ impl<'a> Parser<'a> { } pub fn parse_begin(&mut self) -> Result { - if dialect_of!(self is SnowflakeDialect | BigQueryDialect) { - return self.parse_begin_exception_end(); - } - let modifier = if !self.dialect.supports_start_transaction_modifier() { None } else if self.parse_keyword(Keyword::DEFERRED) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e31e7848f..e4363ff6e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8592,7 +8592,10 @@ fn lateral_function() { #[test] fn parse_start_transaction() { let dialects = all_dialects_except(|d| - // BigQuery does not support this syntax + // BigQuery and Snowflake does not support this syntax + // + // BigQuery: + // Snowflake: d.is::() || d.is::()); match dialects .verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") From 4848e91bf3f9d2d2a1cb53cc898764abfa953d42 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 16 Jun 2025 18:09:25 +0200 Subject: [PATCH 5/7] Fix broken link in docs --- src/ast/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0c637aad2..79fb3c4a9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2999,7 +2999,7 @@ pub struct ExceptionClause { /// RAISE MY_EXCEPTION; /// RAISE USING MESSAGE = "Some error"; /// - /// BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#raise + /// BigQuery: /// Snowflake: // pub raises: Option>, pub raises: Option>, From 985d2e00c561275ab3e26b14e83c8acf71cceb1d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 17 Jun 2025 15:00:50 +0200 Subject: [PATCH 6/7] Drop `ExceptionClause`, fix `Display` --- src/ast/mod.rs | 57 ++++++------------------------------ src/parser/mod.rs | 25 ++++------------ tests/sqlparser_bigquery.rs | 4 +-- tests/sqlparser_snowflake.rs | 10 +++---- 4 files changed, 22 insertions(+), 74 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 79fb3c4a9..aaea202c0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2982,44 +2982,6 @@ impl From for Statement { } } -/// An exception representing exception handling with the `EXCEPTION` keyword. -/// -/// Snowflake: -/// BigQuery: -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct ExceptionClause { - /// When represents each `WHEN` case - pub when: Vec, - /// The exception that is being raised or the current exception being handled if `None`. - /// - /// Example - /// RAISE; - /// RAISE MY_EXCEPTION; - /// RAISE USING MESSAGE = "Some error"; - /// - /// BigQuery: - /// Snowflake: - // pub raises: Option>, - pub raises: Option>, -} - -impl Display for ExceptionClause { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, " EXCEPTION")?; - for w in &self.when { - write!(f, "{w}")?; - } - - if let Some(raises) = &self.raises { - write!(f, " {raises};")?; - } - - Ok(()) - } -} - /// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute /// for the arm. /// @@ -3037,7 +2999,7 @@ impl Display for ExceptionWhen { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - " WHEN {idents} THEN", + "WHEN {idents} THEN", idents = display_separated(&self.idents, " OR ") )?; @@ -3738,11 +3700,9 @@ pub enum Statement { /// END; /// ``` statements: Vec, - /// Exception handling with exception clauses and raises. + /// Exception handling with exception clauses. /// Example: /// ```sql - /// BEGIN - /// SELECT 1; /// EXCEPTION /// WHEN EXCEPTION_1 THEN /// SELECT 2; @@ -3750,12 +3710,10 @@ pub enum Statement { /// SELECT 3; /// WHEN OTHER THEN /// SELECT 4; - /// RAISE; - /// END; /// ``` /// /// - exception: Option, + exception: Option>, /// TRUE if the statement has an `END` keyword. has_end_keyword: bool, }, @@ -5600,7 +5558,7 @@ impl fmt::Display for Statement { transaction, modifier, statements, - exception, + exception: exception_handling, has_end_keyword, } => { if *syntax_begin { @@ -5622,8 +5580,11 @@ impl fmt::Display for Statement { write!(f, " ")?; format_statement_list(f, statements)?; } - if let Some(exception) = exception { - write!(f, "{exception}")?; + if let Some(exception_when) = exception_handling { + write!(f, " EXCEPTION")?; + for when in exception_when { + write!(f, " {when}")?; + } } if *has_end_keyword { write!(f, " END")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 28faf0b62..ce81f6e70 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15136,15 +15136,11 @@ impl<'a> Parser<'a> { pub fn parse_begin_exception_end(&mut self) -> Result { let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; - let exception = if self.parse_keyword(Keyword::EXCEPTION) { + let exception_handling = if self.parse_keyword(Keyword::EXCEPTION) { let mut when = Vec::new(); - // We can have multiple `WHEN` arms so we consume all cases until `END` or an exception - // is `RAISE`ed. - while self - .peek_one_of_keywords(&[Keyword::END, Keyword::RAISE]) - .is_none() - { + // We can have multiple `WHEN` arms so we consume all cases until `END` + while !self.peek_keyword(Keyword::END) { self.expect_keyword(Keyword::WHEN)?; // Each `WHEN` case can have one or more conditions, e.g. @@ -15159,21 +15155,12 @@ impl<'a> Parser<'a> { self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?; } - let statements = - self.parse_statement_list(&[Keyword::WHEN, Keyword::RAISE, Keyword::END])?; + let statements = self.parse_statement_list(&[Keyword::WHEN, Keyword::END])?; when.push(ExceptionWhen { idents, statements }); } - let raises = if self.peek_keyword(Keyword::RAISE) { - let raises = Some(Box::new(self.parse_raise_stmt()?)); - self.expect_token(&Token::SemiColon)?; - raises - } else { - None - }; - - Some(ExceptionClause { when, raises }) + Some(when) } else { None }; @@ -15183,7 +15170,7 @@ impl<'a> Parser<'a> { Ok(Statement::StartTransaction { begin: true, statements, - exception, + exception: exception_handling, has_end_keyword: true, transaction: None, modifier: None, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 87608a651..a4bdfc07f 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -261,7 +261,7 @@ fn parse_at_at_identifier() { #[test] fn parse_begin() { - let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#; + let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; RAISE USING MESSAGE = FORMAT('ERR: %s', 'Bad'); END"#; let Statement::StartTransaction { statements, exception, @@ -275,7 +275,7 @@ fn parse_begin() { assert!(exception.is_some()); let exception = exception.unwrap(); - assert_eq!(1, exception.when.len()); + assert_eq!(1, exception.len()); assert!(has_end_keyword); bigquery().verified_stmt( diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 102aa62c1..453337c42 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4124,9 +4124,9 @@ END assert!(has_end_keyword); let exception = exception.unwrap(); - assert_eq!(3, exception.when.len()); - assert_eq!(1, exception.when[0].idents.len()); - assert_eq!(1, exception.when[0].statements.len()); - assert_eq!(2, exception.when[1].idents.len()); - assert_eq!(2, exception.when[1].statements.len()); + assert_eq!(3, exception.len()); + assert_eq!(1, exception[0].idents.len()); + assert_eq!(1, exception[0].statements.len()); + assert_eq!(2, exception[1].idents.len()); + assert_eq!(2, exception[1].statements.len()); } From 9585be4f7335d65d97ce7c919ffa7f89fa7b3ce2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 17 Jun 2025 15:07:58 +0200 Subject: [PATCH 7/7] Rename bad named variable --- src/ast/mod.rs | 4 ++-- src/parser/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index aaea202c0..5d55db528 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5558,7 +5558,7 @@ impl fmt::Display for Statement { transaction, modifier, statements, - exception: exception_handling, + exception, has_end_keyword, } => { if *syntax_begin { @@ -5580,7 +5580,7 @@ impl fmt::Display for Statement { write!(f, " ")?; format_statement_list(f, statements)?; } - if let Some(exception_when) = exception_handling { + if let Some(exception_when) = exception { write!(f, " EXCEPTION")?; for when in exception_when { write!(f, " {when}")?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ce81f6e70..43b4765ae 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15136,7 +15136,7 @@ impl<'a> Parser<'a> { pub fn parse_begin_exception_end(&mut self) -> Result { let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; - let exception_handling = if self.parse_keyword(Keyword::EXCEPTION) { + let exception = if self.parse_keyword(Keyword::EXCEPTION) { let mut when = Vec::new(); // We can have multiple `WHEN` arms so we consume all cases until `END` @@ -15170,7 +15170,7 @@ impl<'a> Parser<'a> { Ok(Statement::StartTransaction { begin: true, statements, - exception: exception_handling, + exception, has_end_keyword: true, transaction: None, modifier: None,