Skip to content

Commit 1baec96

Browse files
authored
Add support for DEFERRED, IMMEDIATE, and EXCLUSIVE in SQLite's BEGIN TRANSACTION command (#1067)
1 parent 40bc407 commit 1baec96

File tree

7 files changed

+94
-2
lines changed

7 files changed

+94
-2
lines changed

src/ast/mod.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,8 @@ pub enum Statement {
19051905
StartTransaction {
19061906
modes: Vec<TransactionMode>,
19071907
begin: bool,
1908+
/// Only for SQLite
1909+
modifier: Option<TransactionModifier>,
19081910
},
19091911
/// ```sql
19101912
/// SET TRANSACTION ...
@@ -3253,9 +3255,14 @@ impl fmt::Display for Statement {
32533255
Statement::StartTransaction {
32543256
modes,
32553257
begin: syntax_begin,
3258+
modifier,
32563259
} => {
32573260
if *syntax_begin {
3258-
write!(f, "BEGIN TRANSACTION")?;
3261+
if let Some(modifier) = *modifier {
3262+
write!(f, "BEGIN {} TRANSACTION", modifier)?;
3263+
} else {
3264+
write!(f, "BEGIN TRANSACTION")?;
3265+
}
32593266
} else {
32603267
write!(f, "START TRANSACTION")?;
32613268
}
@@ -4444,6 +4451,29 @@ impl fmt::Display for TransactionIsolationLevel {
44444451
}
44454452
}
44464453

4454+
/// SQLite specific syntax
4455+
///
4456+
/// <https://sqlite.org/lang_transaction.html>
4457+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
4458+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4459+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
4460+
pub enum TransactionModifier {
4461+
Deferred,
4462+
Immediate,
4463+
Exclusive,
4464+
}
4465+
4466+
impl fmt::Display for TransactionModifier {
4467+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4468+
use TransactionModifier::*;
4469+
f.write_str(match self {
4470+
Deferred => "DEFERRED",
4471+
Immediate => "IMMEDIATE",
4472+
Exclusive => "EXCLUSIVE",
4473+
})
4474+
}
4475+
}
4476+
44474477
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
44484478
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44494479
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/dialect/generic.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ impl Dialect for GenericDialect {
3838
fn supports_group_by_expr(&self) -> bool {
3939
true
4040
}
41+
42+
fn supports_start_transaction_modifier(&self) -> bool {
43+
true
44+
}
4145
}

src/dialect/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ pub trait Dialect: Debug + Any {
137137
fn supports_in_empty_list(&self) -> bool {
138138
false
139139
}
140+
/// Returns true if the dialect supports `BEGIN {DEFERRED | IMMEDIATE | EXCLUSIVE} [TRANSACTION]` statements
141+
fn supports_start_transaction_modifier(&self) -> bool {
142+
false
143+
}
140144
/// Returns true if the dialect has a CONVERT function which accepts a type first
141145
/// and an expression second, e.g. `CONVERT(varchar, 1)`
142146
fn convert_type_before_value(&self) -> bool {

src/dialect/sqlite.rs

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ impl Dialect for SQLiteDialect {
4040
true
4141
}
4242

43+
fn supports_start_transaction_modifier(&self) -> bool {
44+
true
45+
}
46+
4347
fn is_identifier_part(&self, ch: char) -> bool {
4448
self.is_identifier_start(ch) || ch.is_ascii_digit()
4549
}

src/keywords.rs

+3
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ define_keywords!(
209209
DECIMAL,
210210
DECLARE,
211211
DEFAULT,
212+
DEFERRED,
212213
DELETE,
213214
DELIMITED,
214215
DELIMITER,
@@ -255,6 +256,7 @@ define_keywords!(
255256
EVERY,
256257
EXCEPT,
257258
EXCLUDE,
259+
EXCLUSIVE,
258260
EXEC,
259261
EXECUTE,
260262
EXISTS,
@@ -322,6 +324,7 @@ define_keywords!(
322324
IF,
323325
IGNORE,
324326
ILIKE,
327+
IMMEDIATE,
325328
IMMUTABLE,
326329
IN,
327330
INCLUDE,

src/parser/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -7961,14 +7961,27 @@ impl<'a> Parser<'a> {
79617961
Ok(Statement::StartTransaction {
79627962
modes: self.parse_transaction_modes()?,
79637963
begin: false,
7964+
modifier: None,
79647965
})
79657966
}
79667967

79677968
pub fn parse_begin(&mut self) -> Result<Statement, ParserError> {
7969+
let modifier = if !self.dialect.supports_start_transaction_modifier() {
7970+
None
7971+
} else if self.parse_keyword(Keyword::DEFERRED) {
7972+
Some(TransactionModifier::Deferred)
7973+
} else if self.parse_keyword(Keyword::IMMEDIATE) {
7974+
Some(TransactionModifier::Immediate)
7975+
} else if self.parse_keyword(Keyword::EXCLUSIVE) {
7976+
Some(TransactionModifier::Exclusive)
7977+
} else {
7978+
None
7979+
};
79687980
let _ = self.parse_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK]);
79697981
Ok(Statement::StartTransaction {
79707982
modes: self.parse_transaction_modes()?,
79717983
begin: true,
7984+
modifier,
79727985
})
79737986
}
79747987

tests/sqlparser_sqlite.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use test_utils::*;
2222
use sqlparser::ast::SelectItem::UnnamedExpr;
2323
use sqlparser::ast::*;
2424
use sqlparser::dialect::{GenericDialect, SQLiteDialect};
25-
use sqlparser::parser::ParserOptions;
25+
use sqlparser::parser::{ParserError, ParserOptions};
2626
use sqlparser::tokenizer::Token;
2727

2828
#[test]
@@ -435,6 +435,40 @@ fn invalid_empty_list() {
435435
);
436436
}
437437

438+
#[test]
439+
fn parse_start_transaction_with_modifier() {
440+
sqlite_and_generic().verified_stmt("BEGIN DEFERRED TRANSACTION");
441+
sqlite_and_generic().verified_stmt("BEGIN IMMEDIATE TRANSACTION");
442+
sqlite_and_generic().verified_stmt("BEGIN EXCLUSIVE TRANSACTION");
443+
sqlite_and_generic().one_statement_parses_to("BEGIN DEFERRED", "BEGIN DEFERRED TRANSACTION");
444+
sqlite_and_generic().one_statement_parses_to("BEGIN IMMEDIATE", "BEGIN IMMEDIATE TRANSACTION");
445+
sqlite_and_generic().one_statement_parses_to("BEGIN EXCLUSIVE", "BEGIN EXCLUSIVE TRANSACTION");
446+
447+
let unsupported_dialects = TestedDialects {
448+
dialects: all_dialects()
449+
.dialects
450+
.into_iter()
451+
.filter(|x| !(x.is::<SQLiteDialect>() || x.is::<GenericDialect>()))
452+
.collect(),
453+
options: None,
454+
};
455+
let res = unsupported_dialects.parse_sql_statements("BEGIN DEFERRED");
456+
assert_eq!(
457+
ParserError::ParserError("Expected end of statement, found: DEFERRED".to_string()),
458+
res.unwrap_err(),
459+
);
460+
let res = unsupported_dialects.parse_sql_statements("BEGIN IMMEDIATE");
461+
assert_eq!(
462+
ParserError::ParserError("Expected end of statement, found: IMMEDIATE".to_string()),
463+
res.unwrap_err(),
464+
);
465+
let res = unsupported_dialects.parse_sql_statements("BEGIN EXCLUSIVE");
466+
assert_eq!(
467+
ParserError::ParserError("Expected end of statement, found: EXCLUSIVE".to_string()),
468+
res.unwrap_err(),
469+
);
470+
}
471+
438472
fn sqlite() -> TestedDialects {
439473
TestedDialects {
440474
dialects: vec![Box::new(SQLiteDialect {})],

0 commit comments

Comments
 (0)