Skip to content

Commit d0fce12

Browse files
zzzdongalamb
andauthored
feat(mysql): Increased compatibility for MySQL (#1059)
Co-authored-by: Andrew Lamb <[email protected]>
1 parent f46f147 commit d0fce12

File tree

5 files changed

+187
-15
lines changed

5 files changed

+187
-15
lines changed

src/ast/mod.rs

+72-1
Original file line numberDiff line numberDiff line change
@@ -2047,7 +2047,8 @@ pub enum Statement {
20472047
table_name: ObjectName,
20482048
if_exists: bool,
20492049
},
2050-
///CreateSequence -- define a new sequence
2050+
/// Define a new sequence:
2051+
///
20512052
/// CREATE [ { TEMPORARY | TEMP } ] SEQUENCE [ IF NOT EXISTS ] <sequence_name>
20522053
CreateSequence {
20532054
temporary: bool,
@@ -2068,6 +2069,15 @@ pub enum Statement {
20682069
value: Option<Value>,
20692070
is_eq: bool,
20702071
},
2072+
/// `LOCK TABLES <table_name> [READ [LOCAL] | [LOW_PRIORITY] WRITE]`
2073+
///
2074+
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
2075+
LockTables {
2076+
tables: Vec<LockTable>,
2077+
},
2078+
/// `UNLOCK TABLES`
2079+
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
2080+
UnlockTables,
20712081
}
20722082

20732083
impl fmt::Display for Statement {
@@ -3477,6 +3487,12 @@ impl fmt::Display for Statement {
34773487
}
34783488
Ok(())
34793489
}
3490+
Statement::LockTables { tables } => {
3491+
write!(f, "LOCK TABLES {}", display_comma_separated(tables))
3492+
}
3493+
Statement::UnlockTables => {
3494+
write!(f, "UNLOCK TABLES")
3495+
}
34803496
}
34813497
}
34823498
}
@@ -4979,6 +4995,61 @@ impl fmt::Display for SearchModifier {
49794995
}
49804996
}
49814997

4998+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
4999+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5000+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5001+
pub struct LockTable {
5002+
pub table: Ident,
5003+
pub alias: Option<Ident>,
5004+
pub lock_type: LockTableType,
5005+
}
5006+
5007+
impl fmt::Display for LockTable {
5008+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5009+
let Self {
5010+
table: tbl_name,
5011+
alias,
5012+
lock_type,
5013+
} = self;
5014+
5015+
write!(f, "{tbl_name} ")?;
5016+
if let Some(alias) = alias {
5017+
write!(f, "AS {alias} ")?;
5018+
}
5019+
write!(f, "{lock_type}")?;
5020+
Ok(())
5021+
}
5022+
}
5023+
5024+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5025+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5026+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5027+
pub enum LockTableType {
5028+
Read { local: bool },
5029+
Write { low_priority: bool },
5030+
}
5031+
5032+
impl fmt::Display for LockTableType {
5033+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5034+
match self {
5035+
Self::Read { local } => {
5036+
write!(f, "READ")?;
5037+
if *local {
5038+
write!(f, " LOCAL")?;
5039+
}
5040+
}
5041+
Self::Write { low_priority } => {
5042+
if *low_priority {
5043+
write!(f, "LOW_PRIORITY ")?;
5044+
}
5045+
write!(f, "WRITE")?;
5046+
}
5047+
}
5048+
5049+
Ok(())
5050+
}
5051+
}
5052+
49825053
#[cfg(test)]
49835054
mod tests {
49845055
use super::*;

src/dialect/mysql.rs

+59-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
use alloc::boxed::Box;
1515

1616
use crate::{
17-
ast::{BinaryOperator, Expr},
17+
ast::{BinaryOperator, Expr, LockTable, LockTableType, Statement},
1818
dialect::Dialect,
1919
keywords::Keyword,
20+
parser::{Parser, ParserError},
2021
};
2122

2223
/// A [`Dialect`] for [MySQL](https://www.mysql.com/)
@@ -48,7 +49,7 @@ impl Dialect for MySqlDialect {
4849
parser: &mut crate::parser::Parser,
4950
expr: &crate::ast::Expr,
5051
_precedence: u8,
51-
) -> Option<Result<crate::ast::Expr, crate::parser::ParserError>> {
52+
) -> Option<Result<crate::ast::Expr, ParserError>> {
5253
// Parse DIV as an operator
5354
if parser.parse_keyword(Keyword::DIV) {
5455
Some(Ok(Expr::BinaryOp {
@@ -60,4 +61,60 @@ impl Dialect for MySqlDialect {
6061
None
6162
}
6263
}
64+
65+
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
66+
if parser.parse_keywords(&[Keyword::LOCK, Keyword::TABLES]) {
67+
Some(parse_lock_tables(parser))
68+
} else if parser.parse_keywords(&[Keyword::UNLOCK, Keyword::TABLES]) {
69+
Some(parse_unlock_tables(parser))
70+
} else {
71+
None
72+
}
73+
}
74+
}
75+
76+
/// `LOCK TABLES`
77+
/// <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
78+
fn parse_lock_tables(parser: &mut Parser) -> Result<Statement, ParserError> {
79+
let tables = parser.parse_comma_separated(parse_lock_table)?;
80+
Ok(Statement::LockTables { tables })
81+
}
82+
83+
// tbl_name [[AS] alias] lock_type
84+
fn parse_lock_table(parser: &mut Parser) -> Result<LockTable, ParserError> {
85+
let table = parser.parse_identifier()?;
86+
let alias =
87+
parser.parse_optional_alias(&[Keyword::READ, Keyword::WRITE, Keyword::LOW_PRIORITY])?;
88+
let lock_type = parse_lock_tables_type(parser)?;
89+
90+
Ok(LockTable {
91+
table,
92+
alias,
93+
lock_type,
94+
})
95+
}
96+
97+
// READ [LOCAL] | [LOW_PRIORITY] WRITE
98+
fn parse_lock_tables_type(parser: &mut Parser) -> Result<LockTableType, ParserError> {
99+
if parser.parse_keyword(Keyword::READ) {
100+
if parser.parse_keyword(Keyword::LOCAL) {
101+
Ok(LockTableType::Read { local: true })
102+
} else {
103+
Ok(LockTableType::Read { local: false })
104+
}
105+
} else if parser.parse_keyword(Keyword::WRITE) {
106+
Ok(LockTableType::Write {
107+
low_priority: false,
108+
})
109+
} else if parser.parse_keywords(&[Keyword::LOW_PRIORITY, Keyword::WRITE]) {
110+
Ok(LockTableType::Write { low_priority: true })
111+
} else {
112+
parser.expected("an lock type in LOCK TABLES", parser.peek_token())
113+
}
114+
}
115+
116+
/// UNLOCK TABLES
117+
/// <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
118+
fn parse_unlock_tables(_parser: &mut Parser) -> Result<Statement, ParserError> {
119+
Ok(Statement::UnlockTables)
63120
}

src/keywords.rs

+3
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,11 @@ define_keywords!(
377377
LOCALTIME,
378378
LOCALTIMESTAMP,
379379
LOCATION,
380+
LOCK,
380381
LOCKED,
381382
LOGIN,
382383
LOWER,
384+
LOW_PRIORITY,
383385
MACRO,
384386
MANAGEDLOCATION,
385387
MATCH,
@@ -654,6 +656,7 @@ define_keywords!(
654656
UNION,
655657
UNIQUE,
656658
UNKNOWN,
659+
UNLOCK,
657660
UNLOGGED,
658661
UNNEST,
659662
UNPIVOT,

src/parser/mod.rs

+17-12
Original file line numberDiff line numberDiff line change
@@ -4012,17 +4012,6 @@ impl<'a> Parser<'a> {
40124012
None
40134013
};
40144014

4015-
let comment = if self.parse_keyword(Keyword::COMMENT) {
4016-
let _ = self.consume_token(&Token::Eq);
4017-
let next_token = self.next_token();
4018-
match next_token.token {
4019-
Token::SingleQuotedString(str) => Some(str),
4020-
_ => self.expected("comment", next_token)?,
4021-
}
4022-
} else {
4023-
None
4024-
};
4025-
40264015
let auto_increment_offset = if self.parse_keyword(Keyword::AUTO_INCREMENT) {
40274016
let _ = self.consume_token(&Token::Eq);
40284017
let next_token = self.next_token();
@@ -4097,6 +4086,18 @@ impl<'a> Parser<'a> {
40974086
};
40984087

40994088
let strict = self.parse_keyword(Keyword::STRICT);
4089+
4090+
let comment = if self.parse_keyword(Keyword::COMMENT) {
4091+
let _ = self.consume_token(&Token::Eq);
4092+
let next_token = self.next_token();
4093+
match next_token.token {
4094+
Token::SingleQuotedString(str) => Some(str),
4095+
_ => self.expected("comment", next_token)?,
4096+
}
4097+
} else {
4098+
None
4099+
};
4100+
41004101
Ok(CreateTableBuilder::new(table_name)
41014102
.temporary(temporary)
41024103
.columns(columns)
@@ -4183,7 +4184,7 @@ impl<'a> Parser<'a> {
41834184
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
41844185
let name = self.parse_identifier()?;
41854186
let data_type = self.parse_data_type()?;
4186-
let collation = if self.parse_keyword(Keyword::COLLATE) {
4187+
let mut collation = if self.parse_keyword(Keyword::COLLATE) {
41874188
Some(self.parse_object_name()?)
41884189
} else {
41894190
None
@@ -4202,6 +4203,10 @@ impl<'a> Parser<'a> {
42024203
}
42034204
} else if let Some(option) = self.parse_optional_column_option()? {
42044205
options.push(ColumnOptionDef { name: None, option });
4206+
} else if dialect_of!(self is MySqlDialect | GenericDialect)
4207+
&& self.parse_keyword(Keyword::COLLATE)
4208+
{
4209+
collation = Some(self.parse_object_name()?);
42054210
} else {
42064211
break;
42074212
};

tests/sqlparser_mysql.rs

+36
Original file line numberDiff line numberDiff line change
@@ -1871,6 +1871,42 @@ fn parse_convert_using() {
18711871
mysql().verified_only_select("SELECT CONVERT('test', CHAR CHARACTER SET utf8mb4)");
18721872
}
18731873

1874+
#[test]
1875+
fn parse_create_table_with_column_collate() {
1876+
let sql = "CREATE TABLE tb (id TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci)";
1877+
let canonical = "CREATE TABLE tb (id TEXT COLLATE utf8mb4_0900_ai_ci CHARACTER SET utf8mb4)";
1878+
match mysql().one_statement_parses_to(sql, canonical) {
1879+
Statement::CreateTable { name, columns, .. } => {
1880+
assert_eq!(name.to_string(), "tb");
1881+
assert_eq!(
1882+
vec![ColumnDef {
1883+
name: Ident::new("id"),
1884+
data_type: DataType::Text,
1885+
collation: Some(ObjectName(vec![Ident::new("utf8mb4_0900_ai_ci")])),
1886+
options: vec![ColumnOptionDef {
1887+
name: None,
1888+
option: ColumnOption::CharacterSet(ObjectName(vec![Ident::new("utf8mb4")]))
1889+
}],
1890+
},],
1891+
columns
1892+
);
1893+
}
1894+
_ => unreachable!(),
1895+
}
1896+
}
1897+
1898+
#[test]
1899+
fn parse_lock_tables() {
1900+
mysql().one_statement_parses_to(
1901+
"LOCK TABLES trans t READ, customer WRITE",
1902+
"LOCK TABLES trans AS t READ, customer WRITE",
1903+
);
1904+
mysql().verified_stmt("LOCK TABLES trans AS t READ, customer WRITE");
1905+
mysql().verified_stmt("LOCK TABLES trans AS t READ LOCAL, customer WRITE");
1906+
mysql().verified_stmt("LOCK TABLES trans AS t READ, customer LOW_PRIORITY WRITE");
1907+
mysql().verified_stmt("UNLOCK TABLES");
1908+
}
1909+
18741910
#[test]
18751911
fn parse_json_table() {
18761912
mysql().verified_only_select("SELECT * FROM JSON_TABLE('[[1, 2], [3, 4]]', '$[*]' COLUMNS(a INT PATH '$[0]', b INT PATH '$[1]')) AS t");

0 commit comments

Comments
 (0)