Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"
}),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
83 changes: 83 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://www.postgresql.org/docs/current/sql-set-transaction.html>
let dialects = all_dialects_except(|d|
// BigQuery and Snowflake do not support this syntax
d.is::<BigQueryDialect>() || d.is::<SnowflakeDialect>());

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::<BigQueryDialect>() || d.is::<SnowflakeDialect>() || d.is::<MsSqlDialect>()
})
.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()
);
}
Loading