Skip to content

Extend exception handling #1884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,36 @@ impl From<Set> for Statement {
}
}

/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute
/// for the arm.
///
/// Snowflake: <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
#[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<Ident>,
pub statements: Vec<Statement>,
}

impl Display for ExceptionWhen {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"WHEN {idents} THEN",
idents = display_separated(&self.idents, " OR ")
)?;

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)]
Expand Down Expand Up @@ -3670,17 +3700,20 @@ pub enum Statement {
/// END;
/// ```
statements: Vec<Statement>,
/// Statements of an exception clause.
/// Exception handling with exception clauses.
/// Example:
/// ```sql
/// BEGIN
/// SELECT 1;
/// EXCEPTION WHEN ERROR THEN
/// SELECT 2;
/// SELECT 3;
/// END;
/// EXCEPTION
/// WHEN EXCEPTION_1 THEN
/// SELECT 2;
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
/// SELECT 3;
/// WHEN OTHER THEN
/// SELECT 4;
/// ```
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
exception_statements: Option<Vec<Statement>>,
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/exception>
exception: Option<Vec<ExceptionWhen>>,
/// TRUE if the statement has an `END` keyword.
has_end_keyword: bool,
},
Expand Down Expand Up @@ -5525,7 +5558,7 @@ impl fmt::Display for Statement {
transaction,
modifier,
statements,
exception_statements,
exception,
has_end_keyword,
} => {
if *syntax_begin {
Expand All @@ -5547,11 +5580,10 @@ 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_when) = exception {
write!(f, " EXCEPTION")?;
for when in exception_when {
write!(f, " {when}")?;
}
}
if *has_end_keyword {
Expand Down
51 changes: 5 additions & 46 deletions src/dialect/bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ pub struct BigQueryDialect;

impl Dialect for BigQueryDialect {
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
self.maybe_parse_statement(parser)
if parser.parse_keyword(Keyword::BEGIN) {
return Some(parser.parse_begin_exception_end());
}

None
}

/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
Expand Down Expand Up @@ -141,48 +145,3 @@ impl Dialect for BigQueryDialect {
true
}
}

impl BigQueryDialect {
fn maybe_parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
if parser.peek_keyword(Keyword::BEGIN) {
return Some(self.parse_begin(parser));
}
None
}

/// Parse a `BEGIN` statement.
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
fn parse_begin(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
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(),
})
}
}
4 changes: 4 additions & 0 deletions src/dialect/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ impl Dialect for SnowflakeDialect {
}

fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
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]) {
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ define_keywords!(
ORDER,
ORDINALITY,
ORGANIZATION,
OTHER,
OUT,
OUTER,
OUTPUT,
Expand Down
49 changes: 47 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15096,7 +15096,7 @@ impl<'a> Parser<'a> {
transaction: Some(BeginTransactionKind::Transaction),
modifier: None,
statements: vec![],
exception_statements: None,
exception: None,
has_end_keyword: false,
})
}
Expand Down Expand Up @@ -15128,11 +15128,56 @@ 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<Statement, ParserError> {
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`
while !self.peek_keyword(Keyword::END) {
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::END])?;

when.push(ExceptionWhen { idents, statements });
}

Some(when)
} 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<Statement, ParserError> {
let modifier = if !self.dialect.supports_end_transaction_modifier() {
None
Expand Down
9 changes: 6 additions & 3 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,21 @@ 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_statements,
exception,
has_end_keyword,
..
} = bigquery().verified_stmt(sql)
else {
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.len());
assert!(has_end_keyword);

bigquery().verified_stmt(
Expand Down
7 changes: 5 additions & 2 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8592,8 +8592,11 @@ fn lateral_function() {
#[test]
fn parse_start_transaction() {
let dialects = all_dialects_except(|d|
// BigQuery does not support this syntax
d.is::<BigQueryDialect>());
// BigQuery and Snowflake does not support this syntax
//
// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#begin_transaction>
// Snowflake: <https://docs.snowflake.com/en/sql-reference/sql/begin>
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
match dialects
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
{
Expand Down
64 changes: 64 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.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());
}