Skip to content

Commit

Permalink
Add support for DEFERRED, IMMEDIATE, and EXCLUSIVE in SQLite's BEGIN …
Browse files Browse the repository at this point in the history
…TRANSACTION command (#1067)
  • Loading branch information
takaebato authored Dec 20, 2023
1 parent 40bc407 commit 1baec96
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 2 deletions.
32 changes: 31 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,8 @@ pub enum Statement {
StartTransaction {
modes: Vec<TransactionMode>,
begin: bool,
/// Only for SQLite
modifier: Option<TransactionModifier>,
},
/// ```sql
/// SET TRANSACTION ...
Expand Down Expand Up @@ -3253,9 +3255,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")?;
}
Expand Down Expand Up @@ -4444,6 +4451,29 @@ impl fmt::Display for TransactionIsolationLevel {
}
}

/// SQLite specific syntax
///
/// <https://sqlite.org/lang_transaction.html>
#[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))]
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ impl Dialect for GenericDialect {
fn supports_group_by_expr(&self) -> bool {
true
}

fn supports_start_transaction_modifier(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,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 {
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
3 changes: 3 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ define_keywords!(
DECIMAL,
DECLARE,
DEFAULT,
DEFERRED,
DELETE,
DELIMITED,
DELIMITER,
Expand Down Expand Up @@ -255,6 +256,7 @@ define_keywords!(
EVERY,
EXCEPT,
EXCLUDE,
EXCLUSIVE,
EXEC,
EXECUTE,
EXISTS,
Expand Down Expand Up @@ -322,6 +324,7 @@ define_keywords!(
IF,
IGNORE,
ILIKE,
IMMEDIATE,
IMMUTABLE,
IN,
INCLUDE,
Expand Down
13 changes: 13 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7961,14 +7961,27 @@ impl<'a> Parser<'a> {
Ok(Statement::StartTransaction {
modes: self.parse_transaction_modes()?,
begin: false,
modifier: None,
})
}

pub fn parse_begin(&mut self) -> Result<Statement, ParserError> {
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,
})
}

Expand Down
36 changes: 35 additions & 1 deletion tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -435,6 +435,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::<SQLiteDialect>() || x.is::<GenericDialect>()))
.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 {})],
Expand Down

0 comments on commit 1baec96

Please sign in to comment.