mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-08 01:15:00 +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.)
|
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
@ -3678,17 +3708,20 @@ pub enum Statement {
|
||||||
/// END;
|
/// END;
|
||||||
/// ```
|
/// ```
|
||||||
statements: Vec<Statement>,
|
statements: Vec<Statement>,
|
||||||
/// Statements of an exception clause.
|
/// Exception handling with exception clauses.
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// BEGIN
|
/// EXCEPTION
|
||||||
/// SELECT 1;
|
/// WHEN EXCEPTION_1 THEN
|
||||||
/// EXCEPTION WHEN ERROR THEN
|
/// SELECT 2;
|
||||||
/// SELECT 2;
|
/// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN
|
||||||
/// SELECT 3;
|
/// SELECT 3;
|
||||||
/// END;
|
/// WHEN OTHER THEN
|
||||||
|
/// SELECT 4;
|
||||||
|
/// ```
|
||||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
|
/// <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.
|
/// TRUE if the statement has an `END` keyword.
|
||||||
has_end_keyword: bool,
|
has_end_keyword: bool,
|
||||||
},
|
},
|
||||||
|
@ -5533,7 +5566,7 @@ impl fmt::Display for Statement {
|
||||||
transaction,
|
transaction,
|
||||||
modifier,
|
modifier,
|
||||||
statements,
|
statements,
|
||||||
exception_statements,
|
exception,
|
||||||
has_end_keyword,
|
has_end_keyword,
|
||||||
} => {
|
} => {
|
||||||
if *syntax_begin {
|
if *syntax_begin {
|
||||||
|
@ -5555,11 +5588,10 @@ impl fmt::Display for Statement {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
format_statement_list(f, statements)?;
|
format_statement_list(f, statements)?;
|
||||||
}
|
}
|
||||||
if let Some(exception_statements) = exception_statements {
|
if let Some(exception_when) = exception {
|
||||||
write!(f, " EXCEPTION WHEN ERROR THEN")?;
|
write!(f, " EXCEPTION")?;
|
||||||
if !exception_statements.is_empty() {
|
for when in exception_when {
|
||||||
write!(f, " ")?;
|
write!(f, " {when}")?;
|
||||||
format_statement_list(f, exception_statements)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *has_end_keyword {
|
if *has_end_keyword {
|
||||||
|
|
|
@ -46,7 +46,11 @@ pub struct BigQueryDialect;
|
||||||
|
|
||||||
impl Dialect for BigQueryDialect {
|
impl Dialect for BigQueryDialect {
|
||||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
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>
|
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
|
||||||
|
@ -141,48 +145,3 @@ impl Dialect for BigQueryDialect {
|
||||||
true
|
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>> {
|
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]) {
|
if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
|
||||||
// ALTER SESSION
|
// ALTER SESSION
|
||||||
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
|
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
|
||||||
|
|
|
@ -646,6 +646,7 @@ define_keywords!(
|
||||||
ORDER,
|
ORDER,
|
||||||
ORDINALITY,
|
ORDINALITY,
|
||||||
ORGANIZATION,
|
ORGANIZATION,
|
||||||
|
OTHER,
|
||||||
OUT,
|
OUT,
|
||||||
OUTER,
|
OUTER,
|
||||||
OUTPUT,
|
OUTPUT,
|
||||||
|
|
|
@ -15092,7 +15092,7 @@ impl<'a> Parser<'a> {
|
||||||
transaction: Some(BeginTransactionKind::Transaction),
|
transaction: Some(BeginTransactionKind::Transaction),
|
||||||
modifier: None,
|
modifier: None,
|
||||||
statements: vec![],
|
statements: vec![],
|
||||||
exception_statements: None,
|
exception: None,
|
||||||
has_end_keyword: false,
|
has_end_keyword: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -15124,11 +15124,56 @@ impl<'a> Parser<'a> {
|
||||||
transaction,
|
transaction,
|
||||||
modifier,
|
modifier,
|
||||||
statements: vec![],
|
statements: vec![],
|
||||||
exception_statements: None,
|
exception: None,
|
||||||
has_end_keyword: false,
|
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> {
|
pub fn parse_end(&mut self) -> Result<Statement, ParserError> {
|
||||||
let modifier = if !self.dialect.supports_end_transaction_modifier() {
|
let modifier = if !self.dialect.supports_end_transaction_modifier() {
|
||||||
None
|
None
|
||||||
|
|
|
@ -261,10 +261,10 @@ fn parse_at_at_identifier() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_begin() {
|
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 {
|
let Statement::StartTransaction {
|
||||||
statements,
|
statements,
|
||||||
exception_statements,
|
exception,
|
||||||
has_end_keyword,
|
has_end_keyword,
|
||||||
..
|
..
|
||||||
} = bigquery().verified_stmt(sql)
|
} = bigquery().verified_stmt(sql)
|
||||||
|
@ -272,7 +272,10 @@ fn parse_begin() {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
assert_eq!(1, statements.len());
|
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);
|
assert!(has_end_keyword);
|
||||||
|
|
||||||
bigquery().verified_stmt(
|
bigquery().verified_stmt(
|
||||||
|
|
|
@ -8592,8 +8592,11 @@ fn lateral_function() {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_start_transaction() {
|
fn parse_start_transaction() {
|
||||||
let dialects = all_dialects_except(|d|
|
let dialects = all_dialects_except(|d|
|
||||||
// BigQuery does not support this syntax
|
// BigQuery and Snowflake does not support this syntax
|
||||||
d.is::<BigQueryDialect>());
|
//
|
||||||
|
// 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
|
match dialects
|
||||||
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
|
.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"
|
"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