mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
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
This commit is contained in:
parent
34fedf311d
commit
e4959696b5
6 changed files with 187 additions and 4 deletions
|
@ -11,7 +11,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::boxed::Box;
|
||||
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
@ -19,6 +19,8 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::ast::ObjectName;
|
||||
|
||||
use super::value::escape_single_quote_string;
|
||||
|
||||
/// SQL data types
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
@ -75,6 +77,10 @@ pub enum DataType {
|
|||
Custom(ObjectName),
|
||||
/// Arrays
|
||||
Array(Box<DataType>),
|
||||
/// Enums
|
||||
Enum(Vec<String>),
|
||||
/// Set
|
||||
Set(Vec<String>),
|
||||
}
|
||||
|
||||
impl fmt::Display for DataType {
|
||||
|
@ -116,6 +122,26 @@ impl fmt::Display for DataType {
|
|||
DataType::Bytea => write!(f, "BYTEA"),
|
||||
DataType::Array(ty) => write!(f, "{}[]", ty),
|
||||
DataType::Custom(ty) => write!(f, "{}", ty),
|
||||
DataType::Enum(vals) => {
|
||||
write!(f, "ENUM(")?;
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "'{}'", escape_single_quote_string(v))?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
DataType::Set(vals) => {
|
||||
write!(f, "SET(")?;
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "'{}'", escape_single_quote_string(v))?;
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
//! (commonly referred to as Data Definition Language, or DDL)
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, string::ToString, vec::Vec};
|
||||
use alloc::{boxed::Box, string::String, string::ToString, vec::Vec};
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName};
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
|
@ -338,7 +339,9 @@ pub enum ColumnOption {
|
|||
/// `DEFAULT <restricted-expr>`
|
||||
Default(Expr),
|
||||
/// `{ PRIMARY KEY | UNIQUE }`
|
||||
Unique { is_primary: bool },
|
||||
Unique {
|
||||
is_primary: bool,
|
||||
},
|
||||
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
||||
/// <foreign_table> (<referred_columns>)
|
||||
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||
|
@ -356,6 +359,8 @@ pub enum ColumnOption {
|
|||
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
|
||||
/// - ...
|
||||
DialectSpecific(Vec<Token>),
|
||||
CharacterSet(ObjectName),
|
||||
Comment(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ColumnOption {
|
||||
|
@ -388,6 +393,8 @@ impl fmt::Display for ColumnOption {
|
|||
}
|
||||
Check(expr) => write!(f, "CHECK ({})", expr),
|
||||
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
|
||||
CharacterSet(n) => write!(f, "CHARACTER SET {}", n),
|
||||
Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -734,6 +734,8 @@ pub enum Statement {
|
|||
query: Option<Box<Query>>,
|
||||
without_rowid: bool,
|
||||
like: Option<ObjectName>,
|
||||
engine: Option<String>,
|
||||
default_charset: Option<String>,
|
||||
},
|
||||
/// SQLite's `CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
|
||||
CreateVirtualTable {
|
||||
|
@ -1182,6 +1184,8 @@ impl fmt::Display for Statement {
|
|||
query,
|
||||
without_rowid,
|
||||
like,
|
||||
default_charset,
|
||||
engine,
|
||||
} => {
|
||||
// We want to allow the following options
|
||||
// Empty column list, allowed by PostgreSQL:
|
||||
|
@ -1307,6 +1311,12 @@ impl fmt::Display for Statement {
|
|||
if let Some(query) = query {
|
||||
write!(f, " AS {}", query)?;
|
||||
}
|
||||
if let Some(engine) = engine {
|
||||
write!(f, " ENGINE={}", engine)?;
|
||||
}
|
||||
if let Some(default_charset) = default_charset {
|
||||
write!(f, " DEFAULT CHARSET={}", default_charset)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Statement::CreateVirtualTable {
|
||||
|
|
|
@ -118,6 +118,7 @@ define_keywords!(
|
|||
CHAR,
|
||||
CHARACTER,
|
||||
CHARACTER_LENGTH,
|
||||
CHARSET,
|
||||
CHAR_LENGTH,
|
||||
CHECK,
|
||||
CLOB,
|
||||
|
@ -194,6 +195,8 @@ define_keywords!(
|
|||
END_EXEC = "END-EXEC",
|
||||
END_FRAME,
|
||||
END_PARTITION,
|
||||
ENGINE,
|
||||
ENUM,
|
||||
EQUALS,
|
||||
ERROR,
|
||||
ESCAPE,
|
||||
|
|
|
@ -1522,6 +1522,8 @@ impl<'a> Parser<'a> {
|
|||
query: None,
|
||||
without_rowid: false,
|
||||
like: None,
|
||||
default_charset: None,
|
||||
engine: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1696,6 +1698,26 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
let engine = if self.parse_keyword(Keyword::ENGINE) {
|
||||
self.expect_token(&Token::Eq)?;
|
||||
match self.next_token() {
|
||||
Token::Word(w) => Some(w.value),
|
||||
unexpected => self.expected("identifier", unexpected)?,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let default_charset = if self.parse_keywords(&[Keyword::DEFAULT, Keyword::CHARSET]) {
|
||||
self.expect_token(&Token::Eq)?;
|
||||
match self.next_token() {
|
||||
Token::Word(w) => Some(w.value),
|
||||
unexpected => self.expected("identifier", unexpected)?,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Statement::CreateTable {
|
||||
name: table_name,
|
||||
temporary,
|
||||
|
@ -1713,6 +1735,8 @@ impl<'a> Parser<'a> {
|
|||
query,
|
||||
without_rowid,
|
||||
like,
|
||||
engine,
|
||||
default_charset,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1778,8 +1802,15 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
|
||||
if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
|
||||
if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
|
||||
Ok(Some(ColumnOption::CharacterSet(self.parse_object_name()?)))
|
||||
} else if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) {
|
||||
Ok(Some(ColumnOption::NotNull))
|
||||
} else if self.parse_keywords(&[Keyword::COMMENT]) {
|
||||
match self.next_token() {
|
||||
Token::SingleQuotedString(value, ..) => Ok(Some(ColumnOption::Comment(value))),
|
||||
unexpected => self.expected("string", unexpected),
|
||||
}
|
||||
} else if self.parse_keyword(Keyword::NULL) {
|
||||
Ok(Some(ColumnOption::Null))
|
||||
} else if self.parse_keyword(Keyword::DEFAULT) {
|
||||
|
@ -2301,6 +2332,8 @@ impl<'a> Parser<'a> {
|
|||
let (precision, scale) = self.parse_optional_precision_scale()?;
|
||||
Ok(DataType::Decimal(precision, scale))
|
||||
}
|
||||
Keyword::ENUM => Ok(DataType::Enum(self.parse_string_values()?)),
|
||||
Keyword::SET => Ok(DataType::Set(self.parse_string_values()?)),
|
||||
_ => {
|
||||
self.prev_token();
|
||||
let type_name = self.parse_object_name()?;
|
||||
|
@ -2311,6 +2344,23 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_string_values(&mut self) -> Result<Vec<String>, ParserError> {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let mut values = Vec::new();
|
||||
loop {
|
||||
match self.next_token() {
|
||||
Token::SingleQuotedString(value) => values.push(value),
|
||||
unexpected => self.expected("a string", unexpected)?,
|
||||
}
|
||||
match self.next_token() {
|
||||
Token::Comma => (),
|
||||
Token::RParen => break,
|
||||
unexpected => self.expected(", or }", unexpected)?,
|
||||
}
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
|
||||
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
|
||||
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
|
||||
|
|
|
@ -155,6 +155,93 @@ fn parse_create_table_auto_increment() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_set_enum() {
|
||||
let sql = "CREATE TABLE foo (bar SET('a', 'b'), baz ENUM('a', 'b'))";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable { name, columns, .. } => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("bar"),
|
||||
data_type: DataType::Set(vec!["a".to_string(), "b".to_string()]),
|
||||
collation: None,
|
||||
options: vec![],
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("baz"),
|
||||
data_type: DataType::Enum(vec!["a".to_string(), "b".to_string()]),
|
||||
collation: None,
|
||||
options: vec![],
|
||||
}
|
||||
],
|
||||
columns
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_engine_default_charset() {
|
||||
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable {
|
||||
name,
|
||||
columns,
|
||||
engine,
|
||||
default_charset,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
vec![ColumnDef {
|
||||
name: Ident::new("id"),
|
||||
data_type: DataType::Int(Some(11)),
|
||||
collation: None,
|
||||
options: vec![],
|
||||
},],
|
||||
columns
|
||||
);
|
||||
assert_eq!(engine, Some("InnoDB".to_string()));
|
||||
assert_eq!(default_charset, Some("utf8mb3".to_string()));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_comment_character_set() {
|
||||
let sql = "CREATE TABLE foo (s TEXT CHARACTER SET utf8mb4 COMMENT 'comment')";
|
||||
match mysql().verified_stmt(sql) {
|
||||
Statement::CreateTable { name, columns, .. } => {
|
||||
assert_eq!(name.to_string(), "foo");
|
||||
assert_eq!(
|
||||
vec![ColumnDef {
|
||||
name: Ident::new("s"),
|
||||
data_type: DataType::Text,
|
||||
collation: None,
|
||||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::CharacterSet(ObjectName(vec![Ident::new(
|
||||
"utf8mb4"
|
||||
)]))
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Comment("comment".to_string())
|
||||
}
|
||||
],
|
||||
},],
|
||||
columns
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_quote_identifiers() {
|
||||
let sql = "CREATE TABLE `PRIMARY` (`BEGIN` INT PRIMARY KEY)";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue