mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-24 16:04:04 +00:00
commit
5536cd1f9e
4 changed files with 301 additions and 0 deletions
|
@ -77,6 +77,7 @@ define_keywords!(
|
|||
CAST,
|
||||
CEIL,
|
||||
CEILING,
|
||||
CHAIN,
|
||||
CHAR,
|
||||
CHAR_LENGTH,
|
||||
CHARACTER,
|
||||
|
@ -89,6 +90,7 @@ define_keywords!(
|
|||
COLLECT,
|
||||
COLUMN,
|
||||
COMMIT,
|
||||
COMMITTED,
|
||||
CONDITION,
|
||||
CONNECT,
|
||||
CONSTRAINT,
|
||||
|
@ -194,6 +196,7 @@ define_keywords!(
|
|||
INTERVAL,
|
||||
INTO,
|
||||
IS,
|
||||
ISOLATION,
|
||||
JOIN,
|
||||
KEY,
|
||||
LAG,
|
||||
|
@ -204,6 +207,7 @@ define_keywords!(
|
|||
LEAD,
|
||||
LEADING,
|
||||
LEFT,
|
||||
LEVEL,
|
||||
LIKE,
|
||||
LIKE_REGEX,
|
||||
LIMIT,
|
||||
|
@ -277,6 +281,7 @@ define_keywords!(
|
|||
PROCEDURE,
|
||||
RANGE,
|
||||
RANK,
|
||||
READ,
|
||||
READS,
|
||||
REAL,
|
||||
RECURSIVE,
|
||||
|
@ -294,6 +299,7 @@ define_keywords!(
|
|||
REGR_SXY,
|
||||
REGR_SYY,
|
||||
RELEASE,
|
||||
REPEATABLE,
|
||||
RESTRICT,
|
||||
RESULT,
|
||||
RETURN,
|
||||
|
@ -312,6 +318,7 @@ define_keywords!(
|
|||
SECOND,
|
||||
SELECT,
|
||||
SENSITIVE,
|
||||
SERIALIZABLE,
|
||||
SESSION_USER,
|
||||
SET,
|
||||
SIMILAR,
|
||||
|
@ -350,6 +357,7 @@ define_keywords!(
|
|||
TIMEZONE_MINUTE,
|
||||
TO,
|
||||
TRAILING,
|
||||
TRANSACTION,
|
||||
TRANSLATE,
|
||||
TRANSLATE_REGEX,
|
||||
TRANSLATION,
|
||||
|
@ -361,6 +369,7 @@ define_keywords!(
|
|||
TRUE,
|
||||
UESCAPE,
|
||||
UNBOUNDED,
|
||||
UNCOMMITTED,
|
||||
UNION,
|
||||
UNIQUE,
|
||||
UNKNOWN,
|
||||
|
@ -388,6 +397,8 @@ define_keywords!(
|
|||
WITH,
|
||||
WITHIN,
|
||||
WITHOUT,
|
||||
WRITE,
|
||||
WORK,
|
||||
YEAR,
|
||||
ZONE,
|
||||
END_EXEC = "END-EXEC"
|
||||
|
|
|
@ -416,6 +416,14 @@ pub enum SQLStatement {
|
|||
names: Vec<SQLObjectName>,
|
||||
cascade: bool,
|
||||
},
|
||||
/// { BEGIN [ TRANSACTION | WORK ] | START TRANSACTION } ...
|
||||
SQLStartTransaction { modes: Vec<TransactionMode> },
|
||||
/// SET TRANSACTION ...
|
||||
SQLSetTransaction { modes: Vec<TransactionMode> },
|
||||
/// COMMIT [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
|
||||
SQLCommit { chain: bool },
|
||||
/// ROLLBACK [ TRANSACTION | WORK ] [ AND [ NO ] CHAIN ]
|
||||
SQLRollback { chain: bool },
|
||||
}
|
||||
|
||||
impl ToString for SQLStatement {
|
||||
|
@ -555,6 +563,28 @@ impl ToString for SQLStatement {
|
|||
comma_separated_string(names),
|
||||
if *cascade { " CASCADE" } else { "" },
|
||||
),
|
||||
SQLStatement::SQLStartTransaction { modes } => format!(
|
||||
"START TRANSACTION{}",
|
||||
if modes.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(" {}", comma_separated_string(modes))
|
||||
}
|
||||
),
|
||||
SQLStatement::SQLSetTransaction { modes } => format!(
|
||||
"SET TRANSACTION{}",
|
||||
if modes.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(" {}", comma_separated_string(modes))
|
||||
}
|
||||
),
|
||||
SQLStatement::SQLCommit { chain } => {
|
||||
format!("COMMIT{}", if *chain { " AND CHAIN" } else { "" },)
|
||||
}
|
||||
SQLStatement::SQLRollback { chain } => {
|
||||
format!("ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -706,3 +736,55 @@ impl ToString for SQLOption {
|
|||
format!("{} = {}", self.name.to_string(), self.value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum TransactionMode {
|
||||
AccessMode(TransactionAccessMode),
|
||||
IsolationLevel(TransactionIsolationLevel),
|
||||
}
|
||||
|
||||
impl ToString for TransactionMode {
|
||||
fn to_string(&self) -> String {
|
||||
use TransactionMode::*;
|
||||
match self {
|
||||
AccessMode(access_mode) => access_mode.to_string(),
|
||||
IsolationLevel(iso_level) => format!("ISOLATION LEVEL {}", iso_level.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum TransactionAccessMode {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
impl ToString for TransactionAccessMode {
|
||||
fn to_string(&self) -> String {
|
||||
use TransactionAccessMode::*;
|
||||
match self {
|
||||
ReadOnly => "READ ONLY".into(),
|
||||
ReadWrite => "READ WRITE".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum TransactionIsolationLevel {
|
||||
ReadUncommitted,
|
||||
ReadCommitted,
|
||||
RepeatableRead,
|
||||
Serializable,
|
||||
}
|
||||
|
||||
impl ToString for TransactionIsolationLevel {
|
||||
fn to_string(&self) -> String {
|
||||
use TransactionIsolationLevel::*;
|
||||
match self {
|
||||
ReadUncommitted => "READ UNCOMMITTED".into(),
|
||||
ReadCommitted => "READ COMMITTED".into(),
|
||||
RepeatableRead => "REPEATABLE READ".into(),
|
||||
Serializable => "SERIALIZABLE".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,14 @@ impl Parser {
|
|||
"UPDATE" => Ok(self.parse_update()?),
|
||||
"ALTER" => Ok(self.parse_alter()?),
|
||||
"COPY" => Ok(self.parse_copy()?),
|
||||
"START" => Ok(self.parse_start_transaction()?),
|
||||
"SET" => Ok(self.parse_set_transaction()?),
|
||||
// `BEGIN` is a nonstandard but common alias for the
|
||||
// standard `START TRANSACTION` statement. It is supported
|
||||
// by at least PostgreSQL and MySQL.
|
||||
"BEGIN" => Ok(self.parse_begin()?),
|
||||
"COMMIT" => Ok(self.parse_commit()?),
|
||||
"ROLLBACK" => Ok(self.parse_rollback()?),
|
||||
_ => parser_err!(format!(
|
||||
"Unexpected keyword {:?} at the beginning of a statement",
|
||||
w.to_string()
|
||||
|
@ -1843,6 +1851,86 @@ impl Parser {
|
|||
}
|
||||
Ok(SQLValues(values))
|
||||
}
|
||||
|
||||
pub fn parse_start_transaction(&mut self) -> Result<SQLStatement, ParserError> {
|
||||
self.expect_keyword("TRANSACTION")?;
|
||||
Ok(SQLStatement::SQLStartTransaction {
|
||||
modes: self.parse_transaction_modes()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_begin(&mut self) -> Result<SQLStatement, ParserError> {
|
||||
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
|
||||
Ok(SQLStatement::SQLStartTransaction {
|
||||
modes: self.parse_transaction_modes()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_set_transaction(&mut self) -> Result<SQLStatement, ParserError> {
|
||||
self.expect_keyword("TRANSACTION")?;
|
||||
Ok(SQLStatement::SQLSetTransaction {
|
||||
modes: self.parse_transaction_modes()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_transaction_modes(&mut self) -> Result<Vec<TransactionMode>, ParserError> {
|
||||
let mut modes = vec![];
|
||||
let mut required = false;
|
||||
loop {
|
||||
let mode = if self.parse_keywords(vec!["ISOLATION", "LEVEL"]) {
|
||||
let iso_level = if self.parse_keywords(vec!["READ", "UNCOMMITTED"]) {
|
||||
TransactionIsolationLevel::ReadUncommitted
|
||||
} else if self.parse_keywords(vec!["READ", "COMMITTED"]) {
|
||||
TransactionIsolationLevel::ReadCommitted
|
||||
} else if self.parse_keywords(vec!["REPEATABLE", "READ"]) {
|
||||
TransactionIsolationLevel::RepeatableRead
|
||||
} else if self.parse_keyword("SERIALIZABLE") {
|
||||
TransactionIsolationLevel::Serializable
|
||||
} else {
|
||||
self.expected("isolation level", self.peek_token())?
|
||||
};
|
||||
TransactionMode::IsolationLevel(iso_level)
|
||||
} else if self.parse_keywords(vec!["READ", "ONLY"]) {
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly)
|
||||
} else if self.parse_keywords(vec!["READ", "WRITE"]) {
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite)
|
||||
} else if required || self.peek_token().is_some() {
|
||||
self.expected("transaction mode", self.peek_token())?
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
modes.push(mode);
|
||||
// ANSI requires a comma after each transaction mode, but
|
||||
// PostgreSQL, for historical reasons, does not. We follow
|
||||
// PostgreSQL in making the comma optional, since that is strictly
|
||||
// more general.
|
||||
required = self.consume_token(&Token::Comma);
|
||||
}
|
||||
Ok(modes)
|
||||
}
|
||||
|
||||
pub fn parse_commit(&mut self) -> Result<SQLStatement, ParserError> {
|
||||
Ok(SQLStatement::SQLCommit {
|
||||
chain: self.parse_commit_rollback_chain()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_rollback(&mut self) -> Result<SQLStatement, ParserError> {
|
||||
Ok(SQLStatement::SQLRollback {
|
||||
chain: self.parse_commit_rollback_chain()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_commit_rollback_chain(&mut self) -> Result<bool, ParserError> {
|
||||
let _ = self.parse_one_of_keywords(&["TRANSACTION", "WORK"]);
|
||||
if self.parse_keyword("AND") {
|
||||
let chain = !self.parse_keyword("NO");
|
||||
self.expect_keyword("CHAIN")?;
|
||||
Ok(chain)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SQLWord {
|
||||
|
|
|
@ -2199,6 +2199,126 @@ fn lateral_derived() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_start_transaction() {
|
||||
match verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") {
|
||||
SQLStatement::SQLStartTransaction { modes } => assert_eq!(
|
||||
modes,
|
||||
vec![
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly),
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite),
|
||||
TransactionMode::IsolationLevel(TransactionIsolationLevel::Serializable),
|
||||
]
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// For historical reasons, PostgreSQL allows the commas between the modes to
|
||||
// be omitted.
|
||||
match one_statement_parses_to(
|
||||
"START TRANSACTION READ ONLY READ WRITE ISOLATION LEVEL SERIALIZABLE",
|
||||
"START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE",
|
||||
) {
|
||||
SQLStatement::SQLStartTransaction { modes } => assert_eq!(
|
||||
modes,
|
||||
vec![
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly),
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite),
|
||||
TransactionMode::IsolationLevel(TransactionIsolationLevel::Serializable),
|
||||
]
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
verified_stmt("START TRANSACTION");
|
||||
one_statement_parses_to("BEGIN", "START TRANSACTION");
|
||||
one_statement_parses_to("BEGIN WORK", "START TRANSACTION");
|
||||
one_statement_parses_to("BEGIN TRANSACTION", "START TRANSACTION");
|
||||
|
||||
verified_stmt("START TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
|
||||
verified_stmt("START TRANSACTION ISOLATION LEVEL READ COMMITTED");
|
||||
verified_stmt("START TRANSACTION ISOLATION LEVEL REPEATABLE READ");
|
||||
verified_stmt("START TRANSACTION ISOLATION LEVEL SERIALIZABLE");
|
||||
|
||||
let res = parse_sql_statements("START TRANSACTION ISOLATION LEVEL BAD");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected isolation level, found: BAD".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
|
||||
let res = parse_sql_statements("START TRANSACTION BAD");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected transaction mode, found: BAD".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
|
||||
let res = parse_sql_statements("START TRANSACTION READ ONLY,");
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected transaction mode, found: EOF".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_set_transaction() {
|
||||
// SET TRANSACTION shares transaction mode parsing code with START
|
||||
// TRANSACTION, so no need to duplicate the tests here. We just do a quick
|
||||
// sanity check.
|
||||
match verified_stmt("SET TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") {
|
||||
SQLStatement::SQLSetTransaction { modes } => assert_eq!(
|
||||
modes,
|
||||
vec![
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly),
|
||||
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite),
|
||||
TransactionMode::IsolationLevel(TransactionIsolationLevel::Serializable),
|
||||
]
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_commit() {
|
||||
match verified_stmt("COMMIT") {
|
||||
SQLStatement::SQLCommit { chain: false } => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match verified_stmt("COMMIT AND CHAIN") {
|
||||
SQLStatement::SQLCommit { chain: true } => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
one_statement_parses_to("COMMIT AND NO CHAIN", "COMMIT");
|
||||
one_statement_parses_to("COMMIT WORK AND NO CHAIN", "COMMIT");
|
||||
one_statement_parses_to("COMMIT TRANSACTION AND NO CHAIN", "COMMIT");
|
||||
one_statement_parses_to("COMMIT WORK AND CHAIN", "COMMIT AND CHAIN");
|
||||
one_statement_parses_to("COMMIT TRANSACTION AND CHAIN", "COMMIT AND CHAIN");
|
||||
one_statement_parses_to("COMMIT WORK", "COMMIT");
|
||||
one_statement_parses_to("COMMIT TRANSACTION", "COMMIT");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_rollback() {
|
||||
match verified_stmt("ROLLBACK") {
|
||||
SQLStatement::SQLRollback { chain: false } => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match verified_stmt("ROLLBACK AND CHAIN") {
|
||||
SQLStatement::SQLRollback { chain: true } => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
one_statement_parses_to("ROLLBACK AND NO CHAIN", "ROLLBACK");
|
||||
one_statement_parses_to("ROLLBACK WORK AND NO CHAIN", "ROLLBACK");
|
||||
one_statement_parses_to("ROLLBACK TRANSACTION AND NO CHAIN", "ROLLBACK");
|
||||
one_statement_parses_to("ROLLBACK WORK AND CHAIN", "ROLLBACK AND CHAIN");
|
||||
one_statement_parses_to("ROLLBACK TRANSACTION AND CHAIN", "ROLLBACK AND CHAIN");
|
||||
one_statement_parses_to("ROLLBACK WORK", "ROLLBACK");
|
||||
one_statement_parses_to("ROLLBACK TRANSACTION", "ROLLBACK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue