diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 20058b83a..bfbcdb167 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -9231,12 +9231,14 @@ impl fmt::Display for AttachDuckDBDatabaseOption { #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -/// Mode for transactions: access mode or isolation level. +/// Mode for transactions: access mode, isolation level or deferrability. pub enum TransactionMode { /// Access mode for a transaction (e.g. `READ ONLY` / `READ WRITE`). AccessMode(TransactionAccessMode), /// Isolation level for a transaction (e.g. `SERIALIZABLE`). IsolationLevel(TransactionIsolationLevel), + /// `DEFERRABLE` when true, `NOT DEFERRABLE` when false. + Deferrable(bool), } impl fmt::Display for TransactionMode { @@ -9245,6 +9247,11 @@ impl fmt::Display for TransactionMode { match self { AccessMode(access_mode) => write!(f, "{access_mode}"), IsolationLevel(iso_level) => write!(f, "ISOLATION LEVEL {iso_level}"), + Deferrable(deferrable) => f.write_str(if *deferrable { + "DEFERRABLE" + } else { + "NOT DEFERRABLE" + }), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 15f135fff..9071c2fee 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19910,6 +19910,11 @@ impl<'a> Parser<'a> { TransactionMode::AccessMode(TransactionAccessMode::ReadOnly) } else if self.parse_keywords(&[Keyword::READ, Keyword::WRITE]) { TransactionMode::AccessMode(TransactionAccessMode::ReadWrite) + } else if self.parse_keyword(Keyword::DEFERRABLE) { + TransactionMode::Deferrable(true) + } else if self.parse_keyword(Keyword::NOT) { + self.expect_keyword_is(Keyword::DEFERRABLE)?; + TransactionMode::Deferrable(false) } else if required { self.expected_ref("transaction mode", self.peek_token_ref())? } else { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 2de6062b2..2db349187 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20060,3 +20060,86 @@ fn parse_insert_by_name() { _ => unreachable!(), } } + +#[test] +fn parse_transaction_deferrable_mode() { + // The third transaction mode of the SQL standard, spelled the same in + // PostgreSQL: + let dialects = all_dialects_except(|d| + // BigQuery and Snowflake do not support this syntax + d.is::() || d.is::()); + + let Statement::StartTransaction { modes, .. } = + dialects.verified_stmt("START TRANSACTION DEFERRABLE") + else { + unreachable!() + }; + assert_eq!(modes, vec![TransactionMode::Deferrable(true)]); + + dialects.verified_stmt("BEGIN TRANSACTION NOT DEFERRABLE"); + + // PostgreSQL's `BEGIN` needs no transaction keyword. MsSql is excluded because + // it reads a bare `BEGIN` followed by anything else as a `BEGIN ... END` block. + all_dialects_except(|d| { + d.is::() || d.is::() || d.is::() + }) + .verified_stmt("BEGIN DEFERRABLE"); + + let Statement::StartTransaction { modes, .. } = dialects + .verified_stmt("START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE") + else { + unreachable!() + }; + assert_eq!( + modes, + vec![ + TransactionMode::IsolationLevel(TransactionIsolationLevel::Serializable), + TransactionMode::AccessMode(TransactionAccessMode::ReadOnly), + TransactionMode::Deferrable(true), + ] + ); + + // As with the other modes, PostgreSQL allows the commas to be omitted. + let Statement::StartTransaction { modes, .. } = dialects.one_statement_parses_to( + "START TRANSACTION NOT DEFERRABLE READ WRITE", + "START TRANSACTION NOT DEFERRABLE, READ WRITE", + ) else { + unreachable!() + }; + assert_eq!( + modes, + vec![ + TransactionMode::Deferrable(false), + TransactionMode::AccessMode(TransactionAccessMode::ReadWrite), + ] + ); + + let Statement::Set(Set::SetTransaction { modes, .. }) = + verified_stmt("SET TRANSACTION DEFERRABLE") + else { + unreachable!() + }; + assert_eq!(modes, vec![TransactionMode::Deferrable(true)]); + + let Statement::Set(Set::SetTransaction { modes, session, .. }) = + verified_stmt("SET SESSION CHARACTERISTICS AS TRANSACTION NOT DEFERRABLE") + else { + unreachable!() + }; + assert_eq!(modes, vec![TransactionMode::Deferrable(false)]); + assert!(session); + + assert_eq!( + ParserError::ParserError("Expected: DEFERRABLE, found: SERIALIZABLE".to_string()), + dialects + .parse_sql_statements("START TRANSACTION NOT SERIALIZABLE") + .unwrap_err() + ); + + assert_eq!( + ParserError::ParserError("Expected: transaction mode, found: EOF".to_string()), + dialects + .parse_sql_statements("START TRANSACTION DEFERRABLE,") + .unwrap_err() + ); +}