Skip to content

Commit c4ce2d4

Browse files
Parse the DEFERRABLE transaction mode
1 parent b595062 commit c4ce2d4

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

‎src/ast/mod.rs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9231,12 +9231,14 @@ impl fmt::Display for AttachDuckDBDatabaseOption {
92319231
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
92329232
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92339233
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9234-
/// Mode for transactions: access mode or isolation level.
9234+
/// Mode for transactions: access mode, isolation level or deferrability.
92359235
pub enum TransactionMode {
92369236
/// Access mode for a transaction (e.g. `READ ONLY` / `READ WRITE`).
92379237
AccessMode(TransactionAccessMode),
92389238
/// Isolation level for a transaction (e.g. `SERIALIZABLE`).
92399239
IsolationLevel(TransactionIsolationLevel),
9240+
/// `DEFERRABLE` when true, `NOT DEFERRABLE` when false.
9241+
Deferrable(bool),
92409242
}
92419243

92429244
impl fmt::Display for TransactionMode {
@@ -9245,6 +9247,11 @@ impl fmt::Display for TransactionMode {
92459247
match self {
92469248
AccessMode(access_mode) => write!(f, "{access_mode}"),
92479249
IsolationLevel(iso_level) => write!(f, "ISOLATION LEVEL {iso_level}"),
9250+
Deferrable(deferrable) => f.write_str(if *deferrable {
9251+
"DEFERRABLE"
9252+
} else {
9253+
"NOT DEFERRABLE"
9254+
}),
92489255
}
92499256
}
92509257
}

‎src/parser/mod.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19905,6 +19905,11 @@ impl<'a> Parser<'a> {
1990519905
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly)
1990619906
} else if self.parse_keywords(&[Keyword::READ, Keyword::WRITE]) {
1990719907
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite)
19908+
} else if self.parse_keyword(Keyword::DEFERRABLE) {
19909+
TransactionMode::Deferrable(true)
19910+
} else if self.parse_keyword(Keyword::NOT) {
19911+
self.expect_keyword_is(Keyword::DEFERRABLE)?;
19912+
TransactionMode::Deferrable(false)
1990819913
} else if required {
1990919914
self.expected_ref("transaction mode", self.peek_token_ref())?
1991019915
} else {

‎tests/sqlparser_common.rs‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20015,3 +20015,86 @@ fn parse_function_arg_call_chain_no_exponential_blowup() {
2001520015
rx.recv_timeout(Duration::from_secs(5))
2001620016
.expect("parser should reject this quickly, not loop exponentially");
2001720017
}
20018+
20019+
#[test]
20020+
fn parse_transaction_deferrable_mode() {
20021+
// The third transaction mode of the SQL standard, spelled the same in
20022+
// PostgreSQL: <https://www.postgresql.org/docs/current/sql-set-transaction.html>
20023+
let dialects = all_dialects_except(|d|
20024+
// BigQuery and Snowflake do not support this syntax
20025+
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());
20026+
20027+
match dialects.verified_stmt("START TRANSACTION DEFERRABLE") {
20028+
Statement::StartTransaction { modes, .. } => {
20029+
assert_eq!(modes, vec![TransactionMode::Deferrable(true)])
20030+
}
20031+
_ => unreachable!(),
20032+
}
20033+
20034+
dialects.verified_stmt("BEGIN TRANSACTION NOT DEFERRABLE");
20035+
20036+
// PostgreSQL's `BEGIN` needs no transaction keyword; MsSql reads a bare
20037+
// `BEGIN` followed by anything else as the start of a `BEGIN ... END` block.
20038+
all_dialects_except(|d| {
20039+
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>() || d.is::<MsSqlDialect>()
20040+
})
20041+
.verified_stmt("BEGIN DEFERRABLE");
20042+
20043+
match dialects
20044+
.verified_stmt("START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE")
20045+
{
20046+
Statement::StartTransaction { modes, .. } => assert_eq!(
20047+
modes,
20048+
vec![
20049+
TransactionMode::IsolationLevel(TransactionIsolationLevel::Serializable),
20050+
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly),
20051+
TransactionMode::Deferrable(true),
20052+
]
20053+
),
20054+
_ => unreachable!(),
20055+
}
20056+
20057+
// As with the other modes, PostgreSQL allows the commas to be omitted.
20058+
match dialects.one_statement_parses_to(
20059+
"START TRANSACTION NOT DEFERRABLE READ WRITE",
20060+
"START TRANSACTION NOT DEFERRABLE, READ WRITE",
20061+
) {
20062+
Statement::StartTransaction { modes, .. } => assert_eq!(
20063+
modes,
20064+
vec![
20065+
TransactionMode::Deferrable(false),
20066+
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite),
20067+
]
20068+
),
20069+
_ => unreachable!(),
20070+
}
20071+
20072+
match verified_stmt("SET TRANSACTION DEFERRABLE") {
20073+
Statement::Set(Set::SetTransaction { modes, .. }) => {
20074+
assert_eq!(modes, vec![TransactionMode::Deferrable(true)])
20075+
}
20076+
_ => unreachable!(),
20077+
}
20078+
20079+
match verified_stmt("SET SESSION CHARACTERISTICS AS TRANSACTION NOT DEFERRABLE") {
20080+
Statement::Set(Set::SetTransaction { modes, session, .. }) => {
20081+
assert_eq!(modes, vec![TransactionMode::Deferrable(false)]);
20082+
assert!(session);
20083+
}
20084+
_ => unreachable!(),
20085+
}
20086+
20087+
assert_eq!(
20088+
ParserError::ParserError("Expected: DEFERRABLE, found: SERIALIZABLE".to_string()),
20089+
dialects
20090+
.parse_sql_statements("START TRANSACTION NOT SERIALIZABLE")
20091+
.unwrap_err()
20092+
);
20093+
20094+
assert_eq!(
20095+
ParserError::ParserError("Expected: transaction mode, found: EOF".to_string()),
20096+
dialects
20097+
.parse_sql_statements("START TRANSACTION DEFERRABLE,")
20098+
.unwrap_err()
20099+
);
20100+
}

0 commit comments

Comments
 (0)