Skip to content

Commit 5536cd1

Browse files
authored
Merge pull request #106 from offscale/transactions
Transaction support
2 parents 7e96d81 + 582b25a commit 5536cd1

File tree

4 files changed

+301
-0
lines changed

4 files changed

+301
-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
@@ -416,6 +416,14 @@ pub enum SQLStatement {
416416
names: Vec<SQLObjectName>,
417417
cascade: bool,
418418
},
419+
/// { BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...
420+
SQLStartTransaction { modes: Vec<TransactionMode> },
421+
/// SET TRANSACTION ...
422+
SQLSetTransaction { modes: Vec<TransactionMode> },
423+
/// COMMIT [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
424+
SQLCommit { chain: bool },
425+
/// ROLLBACK [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
426+
SQLRollback { chain: bool },
419427
}
420428

421429
impl ToString for SQLStatement {
@@ -555,6 +563,28 @@ impl ToString for SQLStatement {
555563
comma_separated_string(names),
556564
if *cascade { " CASCADE" } else { "" },
557565
),
566+
SQLStatement::SQLStartTransaction { modes } => format!(
567+
"START TRANSACTION{}",
568+
if modes.is_empty() {
569+
"".into()
570+
} else {
571+
format!(" {}", comma_separated_string(modes))
572+
}
573+
),
574+
SQLStatement::SQLSetTransaction { modes } => format!(
575+
"SET TRANSACTION{}",
576+
if modes.is_empty() {
577+
"".into()
578+
} else {
579+
format!(" {}", comma_separated_string(modes))
580+
}
581+
),
582+
SQLStatement::SQLCommit { chain } => {
583+
format!("COMMIT{}", if *chain { " AND CHAIN" } else { "" },)
584+
}
585+
SQLStatement::SQLRollback { chain } => {
586+
format!("ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },)
587+
}
558588
}
559589
}
560590
}
@@ -706,3 +736,55 @@ impl ToString for SQLOption {
706736
format!("{} = {}", self.name.to_string(), self.value.to_string())
707737
}
708738
}
739+
740+
#[derive(Debug, Clone, PartialEq, Hash)]
741+
pub enum TransactionMode {
742+
AccessMode(TransactionAccessMode),
743+
IsolationLevel(TransactionIsolationLevel),
744+
}
745+
746+
impl ToString for TransactionMode {
747+
fn to_string(&self) -> String {
748+
use TransactionMode::*;
749+
match self {
750+
AccessMode(access_mode) => access_mode.to_string(),
751+
IsolationLevel(iso_level) => format!("ISOLATION LEVEL {}", iso_level.to_string()),
752+
}
753+
}
754+
}
755+
756+
#[derive(Debug, Clone, PartialEq, Hash)]
757+
pub enum TransactionAccessMode {
758+
ReadOnly,
759+
ReadWrite,
760+
}
761+
762+
impl ToString for TransactionAccessMode {
763+
fn to_string(&self) -> String {
764+
use TransactionAccessMode::*;
765+
match self {
766+
ReadOnly => "READ ONLY".into(),
767+
ReadWrite => "READ WRITE".into(),
768+
}
769+
}
770+
}
771+
772+
#[derive(Debug, Clone, PartialEq, Hash)]
773+
pub enum TransactionIsolationLevel {
774+
ReadUncommitted,
775+
ReadCommitted,
776+
RepeatableRead,
777+
Serializable,
778+
}
779+
780+
impl ToString for TransactionIsolationLevel {
781+
fn to_string(&self) -> String {
782+
use TransactionIsolationLevel::*;
783+
match self {
784+
ReadUncommitted => "READ UNCOMMITTED".into(),
785+
ReadCommitted => "READ COMMITTED".into(),
786+
RepeatableRead => "REPEATABLE READ".into(),
787+
Serializable => "SERIALIZABLE".into(),
788+
}
789+
}
790+
}

src/sqlparser.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ 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` is a nonstandard but common alias for the
126+
// standard `START TRANSACTION` statement. It is supported
127+
// by at least PostgreSQL and MySQL.
128+
"BEGIN" => Ok(self.parse_begin()?),
129+
"COMMIT" => Ok(self.parse_commit()?),
130+
"ROLLBACK" => Ok(self.parse_rollback()?),
123131
_ => parser_err!(format!(
124132
"Unexpected keyword {:?} at the beginning of a statement",
125133
w.to_string()
@@ -1843,6 +1851,86 @@ impl Parser {
18431851
}
18441852
Ok(SQLValues(values))
18451853
}
1854+
1855+
pub fn parse_start_transaction(&mut self) -> Result<SQLStatement, ParserError> {
1856+
self.expect_keyword("TRANSACTION")?;
1857+
Ok(SQLStatement::SQLStartTransaction {
1858+
modes: self.parse_transaction_modes()?,
1859+
})
1860+
}
1861+
1862+
pub fn parse_begin(&mut self) -> Result<SQLStatement, ParserError> {
1863+
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
1864+
Ok(SQLStatement::SQLStartTransaction {
1865+
modes: self.parse_transaction_modes()?,
1866+
})
1867+
}
1868+
1869+
pub fn parse_set_transaction(&mut self) -> Result<SQLStatement, ParserError> {
1870+
self.expect_keyword("TRANSACTION")?;
1871+
Ok(SQLStatement::SQLSetTransaction {
1872+
modes: self.parse_transaction_modes()?,
1873+
})
1874+
}
1875+
1876+
pub fn parse_transaction_modes(&mut self) -> Result<Vec<TransactionMode>, ParserError> {
1877+
let mut modes = vec![];
1878+
let mut required = false;
1879+
loop {
1880+
let mode = if self.parse_keywords(vec!["ISOLATION", "LEVEL"]) {
1881+
let iso_level = if self.parse_keywords(vec!["READ", "UNCOMMITTED"]) {
1882+
TransactionIsolationLevel::ReadUncommitted
1883+
} else if self.parse_keywords(vec!["READ", "COMMITTED"]) {
1884+
TransactionIsolationLevel::ReadCommitted
1885+
} else if self.parse_keywords(vec!["REPEATABLE", "READ"]) {
1886+
TransactionIsolationLevel::RepeatableRead
1887+
} else if self.parse_keyword("SERIALIZABLE") {
1888+
TransactionIsolationLevel::Serializable
1889+
} else {
1890+
self.expected("isolation level", self.peek_token())?
1891+
};
1892+
TransactionMode::IsolationLevel(iso_level)
1893+
} else if self.parse_keywords(vec!["READ", "ONLY"]) {
1894+
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly)
1895+
} else if self.parse_keywords(vec!["READ", "WRITE"]) {
1896+
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite)
1897+
} else if required || self.peek_token().is_some() {
1898+
self.expected("transaction mode", self.peek_token())?
1899+
} else {
1900+
break;
1901+
};
1902+
modes.push(mode);
1903+
// ANSI requires a comma after each transaction mode, but
1904+
// PostgreSQL, for historical reasons, does not. We follow
1905+
// PostgreSQL in making the comma optional, since that is strictly
1906+
// more general.
1907+
required = self.consume_token(&Token::Comma);
1908+
}
1909+
Ok(modes)
1910+
}
1911+
1912+
pub fn parse_commit(&mut self) -> Result<SQLStatement, ParserError> {
1913+
Ok(SQLStatement::SQLCommit {
1914+
chain: self.parse_commit_rollback_chain()?,
1915+
})
1916+
}
1917+
1918+
pub fn parse_rollback(&mut self) -> Result<SQLStatement, ParserError> {
1919+
Ok(SQLStatement::SQLRollback {
1920+
chain: self.parse_commit_rollback_chain()?,
1921+
})
1922+
}
1923+
1924+
pub fn parse_commit_rollback_chain(&mut self) -> Result<bool, ParserError> {
1925+
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
1926+
if self.parse_keyword("AND") {
1927+
let chain = !self.parse_keyword("NO");
1928+
self.expect_keyword("CHAIN")?;
1929+
Ok(chain)
1930+
} else {
1931+
Ok(false)
1932+
}
1933+
}
18461934
}
18471935

18481936
impl SQLWord {

0 commit comments

Comments
 (0)