diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4769ea9bd..a6bede5e7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1819,6 +1819,8 @@ pub enum Statement { StartTransaction { modes: Vec, begin: bool, + /// Only for SQLite + modifier: Option, }, /// `SET TRANSACTION ...` SetTransaction { @@ -3107,9 +3109,14 @@ impl fmt::Display for Statement { Statement::StartTransaction { modes, begin: syntax_begin, + modifier, } => { if *syntax_begin { - write!(f, "BEGIN TRANSACTION")?; + if let Some(modifier) = *modifier { + write!(f, "BEGIN {} TRANSACTION", modifier)?; + } else { + write!(f, "BEGIN TRANSACTION")?; + } } else { write!(f, "START TRANSACTION")?; } @@ -4292,6 +4299,29 @@ impl fmt::Display for TransactionIsolationLevel { } } +/// SQLite specific syntax +/// +/// +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum TransactionModifier { + Deferred, + Immediate, + Exclusive, +} + +impl fmt::Display for TransactionModifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TransactionModifier::*; + f.write_str(match self { + Deferred => "DEFERRED", + Immediate => "IMMEDIATE", + Exclusive => "EXCLUSIVE", + }) + } +} + #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index ea5cc6c34..afdaba82d 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -38,4 +38,8 @@ impl Dialect for GenericDialect { fn supports_group_by_expr(&self) -> bool { true } + + fn supports_start_transaction_modifier(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 53bb891de..a10b21254 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -128,6 +128,10 @@ pub trait Dialect: Debug + Any { fn supports_in_empty_list(&self) -> bool { false } + /// Returns true if the dialect supports `BEGIN {DEFERRED | IMMEDIATE | EXCLUSIVE} [TRANSACTION]` statements + fn supports_start_transaction_modifier(&self) -> bool { + false + } /// Returns true if the dialect has a CONVERT function which accepts a type first /// and an expression second, e.g. `CONVERT(varchar, 1)` fn convert_type_before_value(&self) -> bool { diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index c9e9ab185..0640466c8 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -40,6 +40,10 @@ impl Dialect for SQLiteDialect { true } + fn supports_start_transaction_modifier(&self) -> bool { + true + } + fn is_identifier_part(&self, ch: char) -> bool { self.is_identifier_start(ch) || ch.is_ascii_digit() } diff --git a/src/keywords.rs b/src/keywords.rs index 2de36562f..7f13e3990 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -209,6 +209,7 @@ define_keywords!( DECIMAL, DECLARE, DEFAULT, + DEFERRED, DELETE, DELIMITED, DELIMITER, @@ -254,6 +255,7 @@ define_keywords!( EVERY, EXCEPT, EXCLUDE, + EXCLUSIVE, EXEC, EXECUTE, EXISTS, @@ -321,6 +323,7 @@ define_keywords!( IF, IGNORE, ILIKE, + IMMEDIATE, IMMUTABLE, IN, INCLUDE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d9d4761c3..69788acd0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7834,14 +7834,27 @@ impl<'a> Parser<'a> { Ok(Statement::StartTransaction { modes: self.parse_transaction_modes()?, begin: false, + modifier: None, }) } pub fn parse_begin(&mut self) -> Result { + let modifier = if !self.dialect.supports_start_transaction_modifier() { + None + } else if self.parse_keyword(Keyword::DEFERRED) { + Some(TransactionModifier::Deferred) + } else if self.parse_keyword(Keyword::IMMEDIATE) { + Some(TransactionModifier::Immediate) + } else if self.parse_keyword(Keyword::EXCLUSIVE) { + Some(TransactionModifier::Exclusive) + } else { + None + }; let _ = self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK]); Ok(Statement::StartTransaction { modes: self.parse_transaction_modes()?, begin: true, + modifier, }) } diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index cc0d53b14..311085c0a 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -22,7 +22,7 @@ use test_utils::*; use sqlparser::ast::SelectItem::UnnamedExpr; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SQLiteDialect}; -use sqlparser::parser::ParserOptions; +use sqlparser::parser::{ParserError, ParserOptions}; use sqlparser::tokenizer::Token; #[test] @@ -431,6 +431,40 @@ fn invalid_empty_list() { ); } +#[test] +fn parse_start_transaction_with_modifier() { + sqlite_and_generic().verified_stmt("BEGIN DEFERRED TRANSACTION"); + sqlite_and_generic().verified_stmt("BEGIN IMMEDIATE TRANSACTION"); + sqlite_and_generic().verified_stmt("BEGIN EXCLUSIVE TRANSACTION"); + sqlite_and_generic().one_statement_parses_to("BEGIN DEFERRED", "BEGIN DEFERRED TRANSACTION"); + sqlite_and_generic().one_statement_parses_to("BEGIN IMMEDIATE", "BEGIN IMMEDIATE TRANSACTION"); + sqlite_and_generic().one_statement_parses_to("BEGIN EXCLUSIVE", "BEGIN EXCLUSIVE TRANSACTION"); + + let unsupported_dialects = TestedDialects { + dialects: all_dialects() + .dialects + .into_iter() + .filter(|x| !(x.is::() || x.is::())) + .collect(), + options: None, + }; + let res = unsupported_dialects.parse_sql_statements("BEGIN DEFERRED"); + assert_eq!( + ParserError::ParserError("Expected end of statement, found: DEFERRED".to_string()), + res.unwrap_err(), + ); + let res = unsupported_dialects.parse_sql_statements("BEGIN IMMEDIATE"); + assert_eq!( + ParserError::ParserError("Expected end of statement, found: IMMEDIATE".to_string()), + res.unwrap_err(), + ); + let res = unsupported_dialects.parse_sql_statements("BEGIN EXCLUSIVE"); + assert_eq!( + ParserError::ParserError("Expected end of statement, found: EXCLUSIVE".to_string()), + res.unwrap_err(), + ); +} + fn sqlite() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SQLiteDialect {})],