Skip to content

Commit e495969

Browse files
authored
Support CHARSET and ENGINE clauses on CREATE TABLE for mysql (#392)
* Add support for SET and ENUM types See https://dev.mysql.com/doc/refman/8.0/en/set.html and https://dev.mysql.com/doc/refman/8.0/en/enum.html * Add support for mysql ENGINE and DEFAULT CHARSET in CREATE TABLE See https://dev.mysql.com/doc/refman/8.0/en/create-table.html * Add support for COMMENT and CHARACTER SET field attributes See https://dev.mysql.com/doc/refman/8.0/en/create-table.html
1 parent 34fedf3 commit e495969

File tree

6 files changed

+187
-4
lines changed

6 files changed

+187
-4
lines changed

src/ast/data_type.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
// limitations under the License.
1212

1313
#[cfg(not(feature = "std"))]
14-
use alloc::boxed::Box;
14+
use alloc::{boxed::Box, string::String, vec::Vec};
1515
use core::fmt;
1616

1717
#[cfg(feature = "serde")]
1818
use serde::{Deserialize, Serialize};
1919

2020
use crate::ast::ObjectName;
2121

22+
use super::value::escape_single_quote_string;
23+
2224
/// SQL data types
2325
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2426
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -75,6 +77,10 @@ pub enum DataType {
7577
Custom(ObjectName),
7678
/// Arrays
7779
Array(Box<DataType>),
80+
/// Enums
81+
Enum(Vec<String>),
82+
/// Set
83+
Set(Vec<String>),
7884
}
7985

8086
impl fmt::Display for DataType {
@@ -116,6 +122,26 @@ impl fmt::Display for DataType {
116122
DataType::Bytea => write!(f, "BYTEA"),
117123
DataType::Array(ty) => write!(f, "{}[]", ty),
118124
DataType::Custom(ty) => write!(f, "{}", ty),
125+
DataType::Enum(vals) => {
126+
write!(f, "ENUM(")?;
127+
for (i, v) in vals.iter().enumerate() {
128+
if i != 0 {
129+
write!(f, ", ")?;
130+
}
131+
write!(f, "'{}'", escape_single_quote_string(v))?;
132+
}
133+
write!(f, ")")
134+
}
135+
DataType::Set(vals) => {
136+
write!(f, "SET(")?;
137+
for (i, v) in vals.iter().enumerate() {
138+
if i != 0 {
139+
write!(f, ", ")?;
140+
}
141+
write!(f, "'{}'", escape_single_quote_string(v))?;
142+
}
143+
write!(f, ")")
144+
}
119145
}
120146
}
121147
}

src/ast/ddl.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
//! (commonly referred to as Data Definition Language, or DDL)
1515
1616
#[cfg(not(feature = "std"))]
17-
use alloc::{boxed::Box, string::ToString, vec::Vec};
17+
use alloc::{boxed::Box, string::String, string::ToString, vec::Vec};
1818
use core::fmt;
1919

2020
#[cfg(feature = "serde")]
2121
use serde::{Deserialize, Serialize};
2222

23+
use crate::ast::value::escape_single_quote_string;
2324
use crate::ast::{display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName};
2425
use crate::tokenizer::Token;
2526

@@ -338,7 +339,9 @@ pub enum ColumnOption {
338339
/// `DEFAULT <restricted-expr>`
339340
Default(Expr),
340341
/// `{ PRIMARY KEY | UNIQUE }`
341-
Unique { is_primary: bool },
342+
Unique {
343+
is_primary: bool,
344+
},
342345
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
343346
/// <foreign_table> (<referred_columns>)
344347
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
@@ -356,6 +359,8 @@ pub enum ColumnOption {
356359
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
357360
/// - ...
358361
DialectSpecific(Vec<Token>),
362+
CharacterSet(ObjectName),
363+
Comment(String),
359364
}
360365

361366
impl fmt::Display for ColumnOption {
@@ -388,6 +393,8 @@ impl fmt::Display for ColumnOption {
388393
}
389394
Check(expr) => write!(f, "CHECK ({})", expr),
390395
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
396+
CharacterSet(n) => write!(f, "CHARACTER SET {}", n),
397+
Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)),
391398
}
392399
}
393400
}

src/ast/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ pub enum Statement {
734734
query: Option<Box<Query>>,
735735
without_rowid: bool,
736736
like: Option<ObjectName>,
737+
engine: Option<String>,
738+
default_charset: Option<String>,
737739
},
738740
/// SQLite's `CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
739741
CreateVirtualTable {
@@ -1182,6 +1184,8 @@ impl fmt::Display for Statement {
11821184
query,
11831185
without_rowid,
11841186
like,
1187+
default_charset,
1188+
engine,
11851189
} => {
11861190
// We want to allow the following options
11871191
// Empty column list, allowed by PostgreSQL:
@@ -1307,6 +1311,12 @@ impl fmt::Display for Statement {
13071311
if let Some(query) = query {
13081312
write!(f, " AS {}", query)?;
13091313
}
1314+
if let Some(engine) = engine {
1315+
write!(f, " ENGINE={}", engine)?;
1316+
}
1317+
if let Some(default_charset) = default_charset {
1318+
write!(f, " DEFAULT CHARSET={}", default_charset)?;
1319+
}
13101320
Ok(())
13111321
}
13121322
Statement::CreateVirtualTable {

src/keywords.rs

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ define_keywords!(
118118
CHAR,
119119
CHARACTER,
120120
CHARACTER_LENGTH,
121+
CHARSET,
121122
CHAR_LENGTH,
122123
CHECK,
123124
CLOB,
@@ -194,6 +195,8 @@ define_keywords!(
194195
END_EXEC = "END-EXEC",
195196
END_FRAME,
196197
END_PARTITION,
198+
ENGINE,
199+
ENUM,
197200
EQUALS,
198201
ERROR,
199202
ESCAPE,

src/parser.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,8 @@ impl<'a> Parser<'a> {
15221522
query: None,
15231523
without_rowid: false,
15241524
like: None,
1525+
default_charset: None,
1526+
engine: None,
15251527
})
15261528
}
15271529

@@ -1696,6 +1698,26 @@ impl<'a> Parser<'a> {
16961698
None
16971699
};
16981700

1701+
let engine = if self.parse_keyword(Keyword::ENGINE) {
1702+
self.expect_token(&Token::Eq)?;
1703+
match self.next_token() {
1704+
Token::Word(w) => Some(w.value),
1705+
unexpected => self.expected("identifier", unexpected)?,
1706+
}
1707+
} else {
1708+
None
1709+
};
1710+
1711+
let default_charset = if self.parse_keywords(&[Keyword::DEFAULT, Keyword::CHARSET]) {
1712+
self.expect_token(&Token::Eq)?;
1713+
match self.next_token() {
1714+
Token::Word(w) => Some(w.value),
1715+
unexpected => self.expected("identifier", unexpected)?,
1716+
}
1717+
} else {
1718+
None
1719+
};
1720+
16991721
Ok(Statement::CreateTable {
17001722
name: table_name,
17011723
temporary,
@@ -1713,6 +1735,8 @@ impl<'a> Parser<'a> {
17131735
query,
17141736
without_rowid,
17151737
like,
1738+
engine,
1739+
default_charset,
17161740
})
17171741
}
17181742

@@ -1778,8 +1802,15 @@ impl<'a> Parser<'a> {
17781802
}
17791803

17801804
pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
1781-
if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
1805+
if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
1806+
Ok(Some(ColumnOption::CharacterSet(self.parse_object_name()?)))
1807+
} else if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
17821808
Ok(Some(ColumnOption::NotNull))
1809+
} else if self.parse_keywords(&[Keyword::COMMENT]) {
1810+
match self.next_token() {
1811+
Token::SingleQuotedString(value, ..) => Ok(Some(ColumnOption::Comment(value))),
1812+
unexpected => self.expected("string", unexpected),
1813+
}
17831814
} else if self.parse_keyword(Keyword::NULL) {
17841815
Ok(Some(ColumnOption::Null))
17851816
} else if self.parse_keyword(Keyword::DEFAULT) {
@@ -2301,6 +2332,8 @@ impl<'a> Parser<'a> {
23012332
let (precision, scale) = self.parse_optional_precision_scale()?;
23022333
Ok(DataType::Decimal(precision, scale))
23032334
}
2335+
Keyword::ENUM => Ok(DataType::Enum(self.parse_string_values()?)),
2336+
Keyword::SET => Ok(DataType::Set(self.parse_string_values()?)),
23042337
_ => {
23052338
self.prev_token();
23062339
let type_name = self.parse_object_name()?;
@@ -2311,6 +2344,23 @@ impl<'a> Parser<'a> {
23112344
}
23122345
}
23132346

2347+
pub fn parse_string_values(&mut self) -> Result<Vec<String>, ParserError> {
2348+
self.expect_token(&Token::LParen)?;
2349+
let mut values = Vec::new();
2350+
loop {
2351+
match self.next_token() {
2352+
Token::SingleQuotedString(value) => values.push(value),
2353+
unexpected => self.expected("a string", unexpected)?,
2354+
}
2355+
match self.next_token() {
2356+
Token::Comma => (),
2357+
Token::RParen => break,
2358+
unexpected => self.expected(", or }", unexpected)?,
2359+
}
2360+
}
2361+
Ok(values)
2362+
}
2363+
23142364
/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
23152365
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
23162366
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`

tests/sqlparser_mysql.rs

+87
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,93 @@ fn parse_create_table_auto_increment() {
155155
}
156156
}
157157

158+
#[test]
159+
fn parse_create_table_set_enum() {
160+
let sql = "CREATE TABLE foo (bar SET('a', 'b'), baz ENUM('a', 'b'))";
161+
match mysql().verified_stmt(sql) {
162+
Statement::CreateTable { name, columns, .. } => {
163+
assert_eq!(name.to_string(), "foo");
164+
assert_eq!(
165+
vec![
166+
ColumnDef {
167+
name: Ident::new("bar"),
168+
data_type: DataType::Set(vec!["a".to_string(), "b".to_string()]),
169+
collation: None,
170+
options: vec![],
171+
},
172+
ColumnDef {
173+
name: Ident::new("baz"),
174+
data_type: DataType::Enum(vec!["a".to_string(), "b".to_string()]),
175+
collation: None,
176+
options: vec![],
177+
}
178+
],
179+
columns
180+
);
181+
}
182+
_ => unreachable!(),
183+
}
184+
}
185+
186+
#[test]
187+
fn parse_create_table_engine_default_charset() {
188+
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3";
189+
match mysql().verified_stmt(sql) {
190+
Statement::CreateTable {
191+
name,
192+
columns,
193+
engine,
194+
default_charset,
195+
..
196+
} => {
197+
assert_eq!(name.to_string(), "foo");
198+
assert_eq!(
199+
vec![ColumnDef {
200+
name: Ident::new("id"),
201+
data_type: DataType::Int(Some(11)),
202+
collation: None,
203+
options: vec![],
204+
},],
205+
columns
206+
);
207+
assert_eq!(engine, Some("InnoDB".to_string()));
208+
assert_eq!(default_charset, Some("utf8mb3".to_string()));
209+
}
210+
_ => unreachable!(),
211+
}
212+
}
213+
214+
#[test]
215+
fn parse_create_table_comment_character_set() {
216+
let sql = "CREATE TABLE foo (s TEXT CHARACTER SET utf8mb4 COMMENT 'comment')";
217+
match mysql().verified_stmt(sql) {
218+
Statement::CreateTable { name, columns, .. } => {
219+
assert_eq!(name.to_string(), "foo");
220+
assert_eq!(
221+
vec![ColumnDef {
222+
name: Ident::new("s"),
223+
data_type: DataType::Text,
224+
collation: None,
225+
options: vec![
226+
ColumnOptionDef {
227+
name: None,
228+
option: ColumnOption::CharacterSet(ObjectName(vec![Ident::new(
229+
"utf8mb4"
230+
)]))
231+
},
232+
ColumnOptionDef {
233+
name: None,
234+
option: ColumnOption::Comment("comment".to_string())
235+
}
236+
],
237+
},],
238+
columns
239+
);
240+
}
241+
_ => unreachable!(),
242+
}
243+
}
244+
158245
#[test]
159246
fn parse_quote_identifiers() {
160247
let sql = "CREATE TABLE `PRIMARY` (`BEGIN` INT PRIMARY KEY)";

0 commit comments

Comments
 (0)