From 4f56f81d05e81ad16f5cdac58aa6a3f8dec7ce50 Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Wed, 24 Sep 2025 14:36:21 +0300 Subject: [PATCH 1/2] Support BEGIN as standalon clause --- src/parser/mod.rs | 54 ++++++++++++++++++++++++++++-------- tests/sqlparser_snowflake.rs | 34 +++++++++++++++++++++++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4af293976..89bc4e216 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15597,6 +15597,25 @@ impl<'a> Parser<'a> { } pub fn parse_begin_exception_end(&mut self) -> Result { + // Snowflake allows BEGIN as a standalone transaction statement (no END). + // If the next token is a semicolon or EOF, treat it as a standalone BEGIN. + if dialect_of!(self is SnowflakeDialect) { + match &self.peek_token_ref().token { + Token::SemiColon | Token::EOF => { + return Ok(Statement::StartTransaction { + begin: true, + statements: vec![], + exception: None, + has_end_keyword: false, + transaction: None, + modifier: None, + modes: Default::default(), + }) + } + _ => {} + } + } + let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; let exception = if self.parse_keyword(Keyword::EXCEPTION) { @@ -15628,17 +15647,30 @@ impl<'a> Parser<'a> { None }; - self.expect_keyword(Keyword::END)?; - - Ok(Statement::StartTransaction { - begin: true, - statements, - exception, - has_end_keyword: true, - transaction: None, - modifier: None, - modes: Default::default(), - }) + if dialect_of!(self is SnowflakeDialect) { + // Make END optional for Snowflake. If present, set flag accordingly. + let has_end = self.parse_keyword(Keyword::END); + Ok(Statement::StartTransaction { + begin: true, + statements, + exception, + has_end_keyword: has_end, + transaction: None, + modifier: None, + modes: Default::default(), + }) + } else { + 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 { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 5c6fb6231..d1719dd43 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4343,6 +4343,40 @@ fn test_snowflake_fetch_clause_syntax() { ); } +#[test] +fn test_snowflake_begin_standalone() { + // BEGIN; (no END) should be allowed for Snowflake + let mut stmts = snowflake().parse_sql_statements("BEGIN;").unwrap(); + assert_eq!(1, stmts.len()); + match stmts.remove(0) { + Statement::StartTransaction { + begin, + has_end_keyword, + statements, + .. + } => { + assert!(begin); + assert!(!has_end_keyword); + assert!(statements.is_empty()); + } + other => panic!("unexpected stmt: {other:?}"), + } +} + +#[test] +fn test_snowflake_begin_commit_sequence() { + let mut stmts = snowflake().parse_sql_statements("BEGIN; COMMIT;").unwrap(); + assert_eq!(2, stmts.len()); + match stmts.remove(0) { + Statement::StartTransaction { begin, .. } => assert!(begin), + other => panic!("unexpected first stmt: {other:?}"), + } + match stmts.remove(0) { + Statement::Commit { end, .. } => assert!(!end), + other => panic!("unexpected second stmt: {other:?}"), + } +} + #[test] fn test_snowflake_create_view_with_multiple_column_options() { let create_view_with_tag = From bc002e4e87972ad93529bc1ee40e79f3a42f7676 Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Wed, 24 Sep 2025 15:01:09 +0300 Subject: [PATCH 2/2] Only affect snowflake logic --- src/dialect/snowflake.rs | 19 +++++++++++++- src/parser/mod.rs | 54 ++++++++-------------------------------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 116255f8e..59cafe653 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -132,7 +132,24 @@ 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()); + // Allow standalone BEGIN; for Snowflake + match &parser.peek_token_ref().token { + Token::SemiColon | Token::EOF => { + return Some(Ok(Statement::StartTransaction { + modes: Default::default(), + begin: true, + transaction: None, + modifier: None, + statements: vec![], + exception: None, + has_end_keyword: false, + })) + } + _ => { + // BEGIN ... [EXCEPTION] ... END block + return Some(parser.parse_begin_exception_end()); + } + } } if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 89bc4e216..4af293976 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15597,25 +15597,6 @@ impl<'a> Parser<'a> { } pub fn parse_begin_exception_end(&mut self) -> Result { - // Snowflake allows BEGIN as a standalone transaction statement (no END). - // If the next token is a semicolon or EOF, treat it as a standalone BEGIN. - if dialect_of!(self is SnowflakeDialect) { - match &self.peek_token_ref().token { - Token::SemiColon | Token::EOF => { - return Ok(Statement::StartTransaction { - begin: true, - statements: vec![], - exception: None, - has_end_keyword: false, - transaction: None, - modifier: None, - modes: Default::default(), - }) - } - _ => {} - } - } - let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; let exception = if self.parse_keyword(Keyword::EXCEPTION) { @@ -15647,30 +15628,17 @@ impl<'a> Parser<'a> { None }; - if dialect_of!(self is SnowflakeDialect) { - // Make END optional for Snowflake. If present, set flag accordingly. - let has_end = self.parse_keyword(Keyword::END); - Ok(Statement::StartTransaction { - begin: true, - statements, - exception, - has_end_keyword: has_end, - transaction: None, - modifier: None, - modes: Default::default(), - }) - } else { - self.expect_keyword(Keyword::END)?; - Ok(Statement::StartTransaction { - begin: true, - statements, - exception, - has_end_keyword: true, - transaction: None, - modifier: None, - modes: Default::default(), - }) - } + 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 {