mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-25 16:34:04 +00:00
commit
5536cd1f9e
4 changed files with 301 additions and 0 deletions
|
@ -77,6 +77,7 @@ define_keywords!(
|
||||||
CAST,
|
CAST,
|
||||||
CEIL,
|
CEIL,
|
||||||
CEILING,
|
CEILING,
|
||||||
|
CHAIN,
|
||||||
CHAR,
|
CHAR,
|
||||||
CHAR_LENGTH,
|
CHAR_LENGTH,
|
||||||
CHARACTER,
|
CHARACTER,
|
||||||
|
@ -89,6 +90,7 @@ define_keywords!(
|
||||||
COLLECT,
|
COLLECT,
|
||||||
COLUMN,
|
COLUMN,
|
||||||
COMMIT,
|
COMMIT,
|
||||||
|
COMMITTED,
|
||||||
CONDITION,
|
CONDITION,
|
||||||
CONNECT,
|
CONNECT,
|
||||||
CONSTRAINT,
|
CONSTRAINT,
|
||||||
|
@ -194,6 +196,7 @@ define_keywords!(
|
||||||
INTERVAL,
|
INTERVAL,
|
||||||
INTO,
|
INTO,
|
||||||
IS,
|
IS,
|
||||||
|
ISOLATION,
|
||||||
JOIN,
|
JOIN,
|
||||||
KEY,
|
KEY,
|
||||||
LAG,
|
LAG,
|
||||||
|
@ -204,6 +207,7 @@ define_keywords!(
|
||||||
LEAD,
|
LEAD,
|
||||||
LEADING,
|
LEADING,
|
||||||
LEFT,
|
LEFT,
|
||||||
|
LEVEL,
|
||||||
LIKE,
|
LIKE,
|
||||||
LIKE_REGEX,
|
LIKE_REGEX,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
|
@ -277,6 +281,7 @@ define_keywords!(
|
||||||
PROCEDURE,
|
PROCEDURE,
|
||||||
RANGE,
|
RANGE,
|
||||||
RANK,
|
RANK,
|
||||||
|
READ,
|
||||||
READS,
|
READS,
|
||||||
REAL,
|
REAL,
|
||||||
RECURSIVE,
|
RECURSIVE,
|
||||||
|
@ -294,6 +299,7 @@ define_keywords!(
|
||||||
REGR_SXY,
|
REGR_SXY,
|
||||||
REGR_SYY,
|
REGR_SYY,
|
||||||
RELEASE,
|
RELEASE,
|
||||||
|
REPEATABLE,
|
||||||
RESTRICT,
|
RESTRICT,
|
||||||
RESULT,
|
RESULT,
|
||||||
RETURN,
|
RETURN,
|
||||||
|
@ -312,6 +318,7 @@ define_keywords!(
|
||||||
SECOND,
|
SECOND,
|
||||||
SELECT,
|
SELECT,
|
||||||
SENSITIVE,
|
SENSITIVE,
|
||||||
|
SERIALIZABLE,
|
||||||
SESSION_USER,
|
SESSION_USER,
|
||||||
SET,
|
SET,
|
||||||
SIMILAR,
|
SIMILAR,
|
||||||
|
@ -350,6 +357,7 @@ define_keywords!(
|
||||||
TIMEZONE_MINUTE,
|
TIMEZONE_MINUTE,
|
||||||
TO,
|
TO,
|
||||||
TRAILING,
|
TRAILING,
|
||||||
|
TRANSACTION,
|
||||||
TRANSLATE,
|
TRANSLATE,
|
||||||
TRANSLATE_REGEX,
|
TRANSLATE_REGEX,
|
||||||
TRANSLATION,
|
TRANSLATION,
|
||||||
|
@ -361,6 +369,7 @@ define_keywords!(
|
||||||
TRUE,
|
TRUE,
|
||||||
UESCAPE,
|
UESCAPE,
|
||||||
UNBOUNDED,
|
UNBOUNDED,
|
||||||
|
UNCOMMITTED,
|
||||||
UNION,
|
UNION,
|
||||||
UNIQUE,
|
UNIQUE,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
@ -388,6 +397,8 @@ define_keywords!(
|
||||||
WITH,
|
WITH,
|
||||||
WITHIN,
|
WITHIN,
|
||||||
WITHOUT,
|
WITHOUT,
|
||||||
|
WRITE,
|
||||||
|
WORK,
|
||||||
YEAR,
|
YEAR,
|
||||||
ZONE,
|
ZONE,
|
||||||
END_EXEC = "END-EXEC"
|
END_EXEC = "END-EXEC"
|
||||||
|
|
|
@ -416,6 +416,14 @@ pub enum SQLStatement {
|
||||||
names: Vec<SQLObjectName>,
|
names: Vec<SQLObjectName>,
|
||||||
cascade: bool,
|
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 {
|
impl ToString for SQLStatement {
|
||||||
|
@ -555,6 +563,28 @@ impl ToString for SQLStatement {
|
||||||
comma_separated_string(names),
|
comma_separated_string(names),
|
||||||
if *cascade { " CASCADE" } else { "" },
|
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())
|
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()?),
|
"UPDATE" => Ok(self.parse_update()?),
|
||||||
"ALTER" => Ok(self.parse_alter()?),
|
"ALTER" => Ok(self.parse_alter()?),
|
||||||
"COPY" => Ok(self.parse_copy()?),
|
"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!(
|
_ => parser_err!(format!(
|
||||||
"Unexpected keyword {:?} at the beginning of a statement",
|
"Unexpected keyword {:?} at the beginning of a statement",
|
||||||
w.to_string()
|
w.to_string()
|
||||||
|
@ -1843,6 +1851,86 @@ impl Parser {
|
||||||
}
|
}
|
||||||
Ok(SQLValues(values))
|
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 {
|
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]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic(
|
||||||
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"
|
expected = "Parse results with GenericSqlDialect are different from PostgreSqlDialect"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue