mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 23:49:10 +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.
|
// limitations under the License.
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::boxed::Box;
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
@ -19,6 +19,8 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ast::ObjectName;
|
use crate::ast::ObjectName;
|
||||||
|
|
||||||
|
use super::value::escape_single_quote_string;
|
||||||
|
|
||||||
/// SQL data types
|
/// SQL data types
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -75,6 +77,10 @@ pub enum DataType {
|
||||||
Custom(ObjectName),
|
Custom(ObjectName),
|
||||||
/// Arrays
|
/// Arrays
|
||||||
Array(Box<DataType>),
|
Array(Box<DataType>),
|
||||||
|
/// Enums
|
||||||
|
Enum(Vec<String>),
|
||||||
|
/// Set
|
||||||
|
Set(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DataType {
|
impl fmt::Display for DataType {
|
||||||
|
@ -116,6 +122,26 @@ impl fmt::Display for DataType {
|
||||||
DataType::Bytea => write!(f, "BYTEA"),
|
DataType::Bytea => write!(f, "BYTEA"),
|
||||||
DataType::Array(ty) => write!(f, "{}[]", ty),
|
DataType::Array(ty) => write!(f, "{}[]", ty),
|
||||||
DataType::Custom(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)
|
//! (commonly referred to as Data Definition Language, or DDL)
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[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;
|
use core::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
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::ast::{display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
|
|
||||||
|
@ -338,7 +339,9 @@ pub enum ColumnOption {
|
||||||
/// `DEFAULT <restricted-expr>`
|
/// `DEFAULT <restricted-expr>`
|
||||||
Default(Expr),
|
Default(Expr),
|
||||||
/// `{ PRIMARY KEY | UNIQUE }`
|
/// `{ PRIMARY KEY | UNIQUE }`
|
||||||
Unique { is_primary: bool },
|
Unique {
|
||||||
|
is_primary: bool,
|
||||||
|
},
|
||||||
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
|
||||||
/// <foreign_table> (<referred_columns>)
|
/// <foreign_table> (<referred_columns>)
|
||||||
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
|
||||||
|
@ -356,6 +359,8 @@ pub enum ColumnOption {
|
||||||
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
|
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
|
||||||
/// - ...
|
/// - ...
|
||||||
DialectSpecific(Vec<Token>),
|
DialectSpecific(Vec<Token>),
|
||||||
|
CharacterSet(ObjectName),
|
||||||
|
Comment(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ColumnOption {
|
impl fmt::Display for ColumnOption {
|
||||||
|
@ -388,6 +393,8 @@ impl fmt::Display for ColumnOption {
|
||||||
}
|
}
|
||||||
Check(expr) => write!(f, "CHECK ({})", expr),
|
Check(expr) => write!(f, "CHECK ({})", expr),
|
||||||
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
|
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>>,
|
query: Option<Box<Query>>,
|
||||||
without_rowid: bool,
|
without_rowid: bool,
|
||||||
like: Option<ObjectName>,
|
like: Option<ObjectName>,
|
||||||
|
engine: Option<String>,
|
||||||
|
default_charset: Option<String>,
|
||||||
},
|
},
|
||||||
/// SQLite's `CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
|
/// SQLite's `CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
|
||||||
CreateVirtualTable {
|
CreateVirtualTable {
|
||||||
|
@ -1182,6 +1184,8 @@ impl fmt::Display for Statement {
|
||||||
query,
|
query,
|
||||||
without_rowid,
|
without_rowid,
|
||||||
like,
|
like,
|
||||||
|
default_charset,
|
||||||
|
engine,
|
||||||
} => {
|
} => {
|
||||||
// We want to allow the following options
|
// We want to allow the following options
|
||||||
// Empty column list, allowed by PostgreSQL:
|
// Empty column list, allowed by PostgreSQL:
|
||||||
|
@ -1307,6 +1311,12 @@ impl fmt::Display for Statement {
|
||||||
if let Some(query) = query {
|
if let Some(query) = query {
|
||||||
write!(f, " AS {}", 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Statement::CreateVirtualTable {
|
Statement::CreateVirtualTable {
|
||||||
|
|
|
@ -118,6 +118,7 @@ define_keywords!(
|
||||||
CHAR,
|
CHAR,
|
||||||
CHARACTER,
|
CHARACTER,
|
||||||
CHARACTER_LENGTH,
|
CHARACTER_LENGTH,
|
||||||
|
CHARSET,
|
||||||
CHAR_LENGTH,
|
CHAR_LENGTH,
|
||||||
CHECK,
|
CHECK,
|
||||||
CLOB,
|
CLOB,
|
||||||
|
@ -194,6 +195,8 @@ define_keywords!(
|
||||||
END_EXEC = "END-EXEC",
|
END_EXEC = "END-EXEC",
|
||||||
END_FRAME,
|
END_FRAME,
|
||||||
END_PARTITION,
|
END_PARTITION,
|
||||||
|
ENGINE,
|
||||||
|
ENUM,
|
||||||
EQUALS,
|
EQUALS,
|
||||||
ERROR,
|
ERROR,
|
||||||
ESCAPE,
|
ESCAPE,
|
||||||
|
|
|
@ -1522,6 +1522,8 @@ impl<'a> Parser<'a> {
|
||||||
query: None,
|
query: None,
|
||||||
without_rowid: false,
|
without_rowid: false,
|
||||||
like: None,
|
like: None,
|
||||||
|
default_charset: None,
|
||||||
|
engine: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1696,6 +1698,26 @@ impl<'a> Parser<'a> {
|
||||||
None
|
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 {
|
Ok(Statement::CreateTable {
|
||||||
name: table_name,
|
name: table_name,
|
||||||
temporary,
|
temporary,
|
||||||
|
@ -1713,6 +1735,8 @@ impl<'a> Parser<'a> {
|
||||||
query,
|
query,
|
||||||
without_rowid,
|
without_rowid,
|
||||||
like,
|
like,
|
||||||
|
engine,
|
||||||
|
default_charset,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1778,8 +1802,15 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_optional_column_option(&mut self) -> Result<Option<ColumnOption>, ParserError> {
|
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))
|
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) {
|
} else if self.parse_keyword(Keyword::NULL) {
|
||||||
Ok(Some(ColumnOption::Null))
|
Ok(Some(ColumnOption::Null))
|
||||||
} else if self.parse_keyword(Keyword::DEFAULT) {
|
} else if self.parse_keyword(Keyword::DEFAULT) {
|
||||||
|
@ -2301,6 +2332,8 @@ impl<'a> Parser<'a> {
|
||||||
let (precision, scale) = self.parse_optional_precision_scale()?;
|
let (precision, scale) = self.parse_optional_precision_scale()?;
|
||||||
Ok(DataType::Decimal(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();
|
self.prev_token();
|
||||||
let type_name = self.parse_object_name()?;
|
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)
|
/// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword)
|
||||||
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
|
/// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`,
|
||||||
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
|
/// `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]
|
#[test]
|
||||||
fn parse_quote_identifiers() {
|
fn parse_quote_identifiers() {
|
||||||
let sql = "CREATE TABLE `PRIMARY` (`BEGIN` INT PRIMARY KEY)";
|
let sql = "CREATE TABLE `PRIMARY` (`BEGIN` INT PRIMARY KEY)";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue