mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Extend exception handling (#1884)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
This commit is contained in:
parent
185a490218
commit
204d3b484d
8 changed files with 178 additions and 67 deletions
|
@ -2990,6 +2990,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)]
|
||||
|
@ -3678,17 +3708,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
|
||||
/// EXCEPTION
|
||||
/// WHEN EXCEPTION_1 THEN
|
||||
/// SELECT 2;
|
||||
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
|
||||
/// SELECT 3;
|
||||
/// END;
|
||||
/// 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,
|
||||
},
|
||||
|
@ -5533,7 +5566,7 @@ impl fmt::Display for Statement {
|
|||
transaction,
|
||||
modifier,
|
||||
statements,
|
||||
exception_statements,
|
||||
exception,
|
||||
has_end_keyword,
|
||||
} => {
|
||||
if *syntax_begin {
|
||||
|
@ -5555,11 +5588,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 {
|
||||
|
|
|
@ -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>
|
||||
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -646,6 +646,7 @@ define_keywords!(
|
|||
ORDER,
|
||||
ORDINALITY,
|
||||
ORGANIZATION,
|
||||
OTHER,
|
||||
OUT,
|
||||
OUTER,
|
||||
OUTPUT,
|
||||
|
|
|
@ -15092,7 +15092,7 @@ impl<'a> Parser<'a> {
|
|||
transaction: Some(BeginTransactionKind::Transaction),
|
||||
modifier: None,
|
||||
statements: vec![],
|
||||
exception_statements: None,
|
||||
exception: None,
|
||||
has_end_keyword: false,
|
||||
})
|
||||
}
|
||||
|
@ -15124,11 +15124,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
|
||||
|
|
|
@ -261,10 +261,10 @@ 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)
|
||||
|
@ -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.len());
|
||||
assert!(has_end_keyword);
|
||||
|
||||
bigquery().verified_stmt(
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
|
|
|
@ -4082,3 +4082,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());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue