Skip to content

Commit 3ae6f83

Browse files
Agustin Chiappe BerriniSamuelMarksbenesch
committed
Add basic support for transaction statements
Co-authored-by: Samuel Marks <807580+SamuelMarks@users.noreply.github.com> Co-authored-by: Nikhil Benesch <nikhil.benesch@gmail.com>
1 parent 5f9f17d commit 3ae6f83

File tree

4 files changed

+298
-0
lines changed

4 files changed

+298
-0
lines changed

src/dialect/keywords.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ define_keywords!(
7777
CAST,
7878
CEIL,
7979
CEILING,
80+
CHAIN,
8081
CHAR,
8182
CHAR_LENGTH,
8283
CHARACTER,
@@ -89,6 +90,7 @@ define_keywords!(
8990
COLLECT,
9091
COLUMN,
9192
COMMIT,
93+
COMMITTED,
9294
CONDITION,
9395
CONNECT,
9496
CONSTRAINT,
@@ -194,6 +196,7 @@ define_keywords!(
194196
INTERVAL,
195197
INTO,
196198
IS,
199+
ISOLATION,
197200
JOIN,
198201
KEY,
199202
LAG,
@@ -204,6 +207,7 @@ define_keywords!(
204207
LEAD,
205208
LEADING,
206209
LEFT,
210+
LEVEL,
207211
LIKE,
208212
LIKE_REGEX,
209213
LIMIT,
@@ -277,6 +281,7 @@ define_keywords!(
277281
PROCEDURE,
278282
RANGE,
279283
RANK,
284+
READ,
280285
READS,
281286
REAL,
282287
RECURSIVE,
@@ -294,6 +299,7 @@ define_keywords!(
294299
REGR_SXY,
295300
REGR_SYY,
296301
RELEASE,
302+
REPEATABLE,
297303
RESTRICT,
298304
RESULT,
299305
RETURN,
@@ -312,6 +318,7 @@ define_keywords!(
312318
SECOND,
313319
SELECT,
314320
SENSITIVE,
321+
SERIALIZABLE,
315322
SESSION_USER,
316323
SET,
317324
SIMILAR,
@@ -350,6 +357,7 @@ define_keywords!(
350357
TIMEZONE_MINUTE,
351358
TO,
352359
TRAILING,
360+
TRANSACTION,
353361
TRANSLATE,
354362
TRANSLATE_REGEX,
355363
TRANSLATION,
@@ -361,6 +369,7 @@ define_keywords!(
361369
TRUE,
362370
UESCAPE,
363371
UNBOUNDED,
372+
UNCOMMITTED,
364373
UNION,
365374
UNIQUE,
366375
UNKNOWN,
@@ -388,6 +397,8 @@ define_keywords!(
388397
WITH,
389398
WITHIN,
390399
WITHOUT,
400+
WRITE,
401+
WORK,
391402
YEAR,
392403
ZONE,
393404
END_EXEC = "END-EXEC"

src/sqlast/mod.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ pub enum SQLStatement {
414414
names: Vec<SQLObjectName>,
415415
cascade: bool,
416416
},
417+
/// { BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...
418+
SQLStartTransaction { modes: Vec<TransactionMode> },
419+
/// SET TRANSACTION ...
420+
SQLSetTransaction { modes: Vec<TransactionMode> },
421+
/// COMMIT [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
422+
SQLCommit { chain: bool },
423+
/// ROLLBACK [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
424+
SQLRollback { chain: bool },
417425
}
418426

419427
impl ToString for SQLStatement {
@@ -553,6 +561,28 @@ impl ToString for SQLStatement {
553561
comma_separated_string(names),
554562
if *cascade { " CASCADE" } else { "" },
555563
),
564+
SQLStatement::SQLStartTransaction { modes } => format!(
565+
"START TRANSACTION{}",
566+
if modes.is_empty() {
567+
"".into()
568+
} else {
569+
format!(" {}", comma_separated_string(modes))
570+
}
571+
),
572+
SQLStatement::SQLSetTransaction { modes } => format!(
573+
"SET TRANSACTION{}",
574+
if modes.is_empty() {
575+
"".into()
576+
} else {
577+
format!(" {}", comma_separated_string(modes))
578+
}
579+
),
580+
SQLStatement::SQLCommit { chain } => {
581+
format!("COMMIT{}", if *chain { " AND CHAIN" } else { "" },)
582+
}
583+
SQLStatement::SQLRollback { chain } => {
584+
format!("ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },)
585+
}
556586
}
557587
}
558588
}
@@ -734,3 +764,55 @@ impl ToString for SQLOption {
734764
format!("{} = {}", self.name.to_string(), self.value.to_string())
735765
}
736766
}
767+
768+
#[derive(Debug, Clone, PartialEq, Hash)]
769+
pub enum TransactionMode {
770+
AccessMode(TransactionAccessMode),
771+
IsolationLevel(TransactionIsolationLevel),
772+
}
773+
774+
impl ToString for TransactionMode {
775+
fn to_string(&self) -> String {
776+
use TransactionMode::*;
777+
match self {
778+
AccessMode(access_mode) => access_mode.to_string(),
779+
IsolationLevel(iso_level) => format!("ISOLATION LEVEL {}", iso_level.to_string()),
780+
}
781+
}
782+
}
783+
784+
#[derive(Debug, Clone, PartialEq, Hash)]
785+
pub enum TransactionAccessMode {
786+
ReadOnly,
787+
ReadWrite,
788+
}
789+
790+
impl ToString for TransactionAccessMode {
791+
fn to_string(&self) -> String {
792+
use TransactionAccessMode::*;
793+
match self {
794+
ReadOnly => "READ ONLY".into(),
795+
ReadWrite => "READ WRITE".into(),
796+
}
797+
}
798+
}
799+
800+
#[derive(Debug, Clone, PartialEq, Hash)]
801+
pub enum TransactionIsolationLevel {
802+
ReadUncommitted,
803+
ReadCommitted,
804+
RepeatableRead,
805+
Serializable,
806+
}
807+
808+
impl ToString for TransactionIsolationLevel {
809+
fn to_string(&self) -> String {
810+
use TransactionIsolationLevel::*;
811+
match self {
812+
ReadUncommitted => "READ UNCOMMITTED".into(),
813+
ReadCommitted => "READ COMMITTED".into(),
814+
RepeatableRead => "REPEATABLE READ".into(),
815+
Serializable => "SERIALIZABLE".into(),
816+
}
817+
}
818+
}

src/sqlparser.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ impl Parser {
120120
"UPDATE" => Ok(self.parse_update()?),
121121
"ALTER" => Ok(self.parse_alter()?),
122122
"COPY" => Ok(self.parse_copy()?),
123+
"START" => Ok(self.parse_start_transaction()?),
124+
"SET" => Ok(self.parse_set_transaction()?),
125+
"BEGIN" => Ok(self.parse_begin()?),
126+
"COMMIT" => Ok(self.parse_commit()?),
127+
"ROLLBACK" => Ok(self.parse_rollback()?),
123128
_ => parser_err!(format!(
124129
"Unexpected keyword {:?} at the beginning of a statement",
125130
w.to_string()
@@ -1832,6 +1837,86 @@ impl Parser {
18321837
}
18331838
Ok(SQLValues(values))
18341839
}
1840+
1841+
pub fn parse_start_transaction(&mut self) -> Result<SQLStatement, ParserError> {
1842+
self.expect_keyword("TRANSACTION")?;
1843+
Ok(SQLStatement::SQLStartTransaction {
1844+
modes: self.parse_transaction_modes()?,
1845+
})
1846+
}
1847+
1848+
pub fn parse_begin(&mut self) -> Result<SQLStatement, ParserError> {
1849+
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
1850+
Ok(SQLStatement::SQLStartTransaction {
1851+
modes: self.parse_transaction_modes()?,
1852+
})
1853+
}
1854+
1855+
pub fn parse_set_transaction(&mut self) -> Result<SQLStatement, ParserError> {
1856+
self.expect_keyword("TRANSACTION")?;
1857+
Ok(SQLStatement::SQLSetTransaction {
1858+
modes: self.parse_transaction_modes()?,
1859+
})
1860+
}
1861+
1862+
pub fn parse_transaction_modes(&mut self) -> Result<Vec<TransactionMode>, ParserError> {
1863+
let mut modes = vec![];
1864+
let mut required = false;
1865+
loop {
1866+
let mode = if self.parse_keywords(vec!["ISOLATION", "LEVEL"]) {
1867+
let iso_level = if self.parse_keywords(vec!["READ", "UNCOMMITTED"]) {
1868+
TransactionIsolationLevel::ReadUncommitted
1869+
} else if self.parse_keywords(vec!["READ", "COMMITTED"]) {
1870+
TransactionIsolationLevel::ReadCommitted
1871+
} else if self.parse_keywords(vec!["REPEATABLE", "READ"]) {
1872+
TransactionIsolationLevel::RepeatableRead
1873+
} else if self.parse_keyword("SERIALIZABLE") {
1874+
TransactionIsolationLevel::Serializable
1875+
} else {
1876+
self.expected("isolation level", self.peek_token())?
1877+
};
1878+
TransactionMode::IsolationLevel(iso_level)
1879+
} else if self.parse_keywords(vec!["READ", "ONLY"]) {
1880+
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly)
1881+
} else if self.parse_keywords(vec!["READ", "WRITE"]) {
1882+
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite)
1883+
} else if required || self.peek_token().is_some() {
1884+
self.expected("transaction mode", self.peek_token())?
1885+
} else {
1886+
break;
1887+
};
1888+
modes.push(mode);
1889+
// ANSI requires a comma after each transaction mode, but
1890+
// PostgreSQL, for historical reasons, does not. We follow
1891+
// PostgreSQL in making the comma optional, since that is strictly
1892+
// more general.
1893+
required = self.consume_token(&Token::Comma);
1894+
}
1895+
Ok(modes)
1896+
}
1897+
1898+
pub fn parse_commit(&mut self) -> Result<SQLStatement, ParserError> {
1899+
Ok(SQLStatement::SQLCommit {
1900+
chain: self.parse_commit_rollback_chain()?,
1901+
})
1902+
}
1903+
1904+
pub fn parse_rollback(&mut self) -> Result<SQLStatement, ParserError> {
1905+
Ok(SQLStatement::SQLRollback {
1906+
chain: self.parse_commit_rollback_chain()?,
1907+
})
1908+
}
1909+
1910+
pub fn parse_commit_rollback_chain(&mut self) -> Result<bool, ParserError> {
1911+
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
1912+
if self.parse_keyword("AND") {
1913+
let chain = !self.parse_keyword("NO");
1914+
self.expect_keyword("CHAIN")?;
1915+
Ok(chain)
1916+
} else {
1917+
Ok(false)
1918+
}
1919+
}
18351920
}
18361921

18371922
impl SQLWord {

0 commit comments

Comments
 (0)