mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Adding MySQL table option {INDEX | KEY} to the CREATE TABLE definiton (partial). (#665)
Theoretically the behavior should be the same as CREATE INDEX, but we cannot make that assumption, so the parse is (almost) identical as the input. Breaking changes: - Now HASH and BTREE are KEYWORDS, and using them as names can result in errors. - Now 'KEY' and 'INDEX' column names start the parsing of a table constraint if unquoted for the Generic dialect. This results in possible conficts if canonical results are compared for all dialects if a column is named 'key' without quotes.
This commit is contained in:
parent
e3c936a6ce
commit
2aba3f8c91
6 changed files with 242 additions and 5 deletions
|
@ -247,6 +247,24 @@ pub enum TableConstraint {
|
||||||
name: Option<Ident>,
|
name: Option<Ident>,
|
||||||
expr: Box<Expr>,
|
expr: Box<Expr>,
|
||||||
},
|
},
|
||||||
|
/// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
|
||||||
|
/// is restricted to MySQL, as no other dialects that support this syntax were found.
|
||||||
|
///
|
||||||
|
/// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...`
|
||||||
|
///
|
||||||
|
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
|
||||||
|
Index {
|
||||||
|
/// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax.
|
||||||
|
display_as_key: bool,
|
||||||
|
/// Index name.
|
||||||
|
name: Option<Ident>,
|
||||||
|
/// Optional [index type][1].
|
||||||
|
///
|
||||||
|
/// [1]: IndexType
|
||||||
|
index_type: Option<IndexType>,
|
||||||
|
/// Referred column identifier list.
|
||||||
|
columns: Vec<Ident>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TableConstraint {
|
impl fmt::Display for TableConstraint {
|
||||||
|
@ -290,6 +308,48 @@ impl fmt::Display for TableConstraint {
|
||||||
TableConstraint::Check { name, expr } => {
|
TableConstraint::Check { name, expr } => {
|
||||||
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
|
write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
|
||||||
}
|
}
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key,
|
||||||
|
name,
|
||||||
|
index_type,
|
||||||
|
columns,
|
||||||
|
} => {
|
||||||
|
write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
write!(f, " {}", name)?;
|
||||||
|
}
|
||||||
|
if let Some(index_type) = index_type {
|
||||||
|
write!(f, " USING {}", index_type)?;
|
||||||
|
}
|
||||||
|
write!(f, " ({})", display_comma_separated(columns))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indexing method used by that index.
|
||||||
|
///
|
||||||
|
/// This structure isn't present on ANSI, but is found at least in [MySQL CREATE TABLE][1],
|
||||||
|
/// [MySQL CREATE INDEX][2], and [Postgresql CREATE INDEX][3] statements.
|
||||||
|
///
|
||||||
|
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
|
||||||
|
/// [2]: https://dev.mysql.com/doc/refman/8.0/en/create-index.html
|
||||||
|
/// [3]: https://www.postgresql.org/docs/14/sql-createindex.html
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum IndexType {
|
||||||
|
BTree,
|
||||||
|
Hash,
|
||||||
|
// TODO add Postgresql's possible indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IndexType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::BTree => write!(f, "BTREE"),
|
||||||
|
Self::Hash => write!(f, "HASH"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub use self::data_type::{
|
||||||
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, TimezoneInfo,
|
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, TimezoneInfo,
|
||||||
};
|
};
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
|
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, IndexType,
|
||||||
ReferentialAction, TableConstraint,
|
ReferentialAction, TableConstraint,
|
||||||
};
|
};
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
|
|
|
@ -105,6 +105,7 @@ define_keywords!(
|
||||||
BLOB,
|
BLOB,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
BOTH,
|
BOTH,
|
||||||
|
BTREE,
|
||||||
BY,
|
BY,
|
||||||
BYPASSRLS,
|
BYPASSRLS,
|
||||||
BYTEA,
|
BYTEA,
|
||||||
|
@ -265,6 +266,7 @@ define_keywords!(
|
||||||
GROUP,
|
GROUP,
|
||||||
GROUPING,
|
GROUPING,
|
||||||
GROUPS,
|
GROUPS,
|
||||||
|
HASH,
|
||||||
HAVING,
|
HAVING,
|
||||||
HEADER,
|
HEADER,
|
||||||
HIVEVAR,
|
HIVEVAR,
|
||||||
|
|
131
src/parser.rs
131
src/parser.rs
|
@ -3003,6 +3003,31 @@ impl<'a> Parser<'a> {
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
Ok(Some(TableConstraint::Check { name, expr }))
|
Ok(Some(TableConstraint::Check { name, expr }))
|
||||||
}
|
}
|
||||||
|
Token::Word(w)
|
||||||
|
if (w.keyword == Keyword::INDEX || w.keyword == Keyword::KEY)
|
||||||
|
&& dialect_of!(self is GenericDialect | MySqlDialect) =>
|
||||||
|
{
|
||||||
|
let display_as_key = w.keyword == Keyword::KEY;
|
||||||
|
|
||||||
|
let name = match self.peek_token() {
|
||||||
|
Token::Word(word) if word.keyword == Keyword::USING => None,
|
||||||
|
_ => self.maybe_parse(|parser| parser.parse_identifier()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let index_type = if self.parse_keyword(Keyword::USING) {
|
||||||
|
Some(self.parse_index_type()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let columns = self.parse_parenthesized_column_list(Mandatory)?;
|
||||||
|
|
||||||
|
Ok(Some(TableConstraint::Index {
|
||||||
|
display_as_key,
|
||||||
|
name,
|
||||||
|
index_type,
|
||||||
|
columns,
|
||||||
|
}))
|
||||||
|
}
|
||||||
unexpected => {
|
unexpected => {
|
||||||
if name.is_some() {
|
if name.is_some() {
|
||||||
self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK", unexpected)
|
self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK", unexpected)
|
||||||
|
@ -3025,6 +3050,16 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_index_type(&mut self) -> Result<IndexType, ParserError> {
|
||||||
|
if self.parse_keyword(Keyword::BTREE) {
|
||||||
|
Ok(IndexType::BTree)
|
||||||
|
} else if self.parse_keyword(Keyword::HASH) {
|
||||||
|
Ok(IndexType::Hash)
|
||||||
|
} else {
|
||||||
|
self.expected("index type {BTREE | HASH}", self.peek_token())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
|
pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
|
||||||
let name = self.parse_identifier()?;
|
let name = self.parse_identifier()?;
|
||||||
self.expect_token(&Token::Eq)?;
|
self.expect_token(&Token::Eq)?;
|
||||||
|
@ -5779,4 +5814,100 @@ mod tests {
|
||||||
SchemaName::NamedAuthorization(dummy_name.clone(), dummy_authorization.clone()),
|
SchemaName::NamedAuthorization(dummy_name.clone(), dummy_authorization.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mysql_parse_index_table_constraint() {
|
||||||
|
macro_rules! test_parse_table_constraint {
|
||||||
|
($dialect:expr, $input:expr, $expected:expr $(,)?) => {{
|
||||||
|
$dialect.run_parser_method(&*$input, |parser| {
|
||||||
|
let constraint = parser.parse_optional_table_constraint().unwrap().unwrap();
|
||||||
|
// Validate that the structure is the same as expected
|
||||||
|
assert_eq!(constraint, $expected);
|
||||||
|
// Validate that the input and the expected structure serialization are the same
|
||||||
|
assert_eq!(constraint.to_string(), $input.to_string());
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialect = TestedDialects {
|
||||||
|
dialects: vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})],
|
||||||
|
};
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: None,
|
||||||
|
index_type: None,
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"KEY (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: true,
|
||||||
|
name: None,
|
||||||
|
index_type: None,
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX 'index' (c1, c2)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: Some(Ident::with_quote('\'', "index")),
|
||||||
|
index_type: None,
|
||||||
|
columns: vec![Ident::new("c1"), Ident::new("c2")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX USING BTREE (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: None,
|
||||||
|
index_type: Some(IndexType::BTree),
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX USING HASH (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: None,
|
||||||
|
index_type: Some(IndexType::Hash),
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX idx_name USING BTREE (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: Some(Ident::new("idx_name")),
|
||||||
|
index_type: Some(IndexType::BTree),
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test_parse_table_constraint!(
|
||||||
|
dialect,
|
||||||
|
"INDEX idx_name USING HASH (c1)",
|
||||||
|
TableConstraint::Index {
|
||||||
|
display_as_key: false,
|
||||||
|
name: Some(Ident::new("idx_name")),
|
||||||
|
index_type: Some(IndexType::Hash),
|
||||||
|
columns: vec![Ident::new("c1")],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2089,10 +2089,10 @@ fn parse_create_table_hive_array() {
|
||||||
let dialects = TestedDialects {
|
let dialects = TestedDialects {
|
||||||
dialects: vec![Box::new(PostgreSqlDialect {}), Box::new(HiveDialect {})],
|
dialects: vec![Box::new(PostgreSqlDialect {}), Box::new(HiveDialect {})],
|
||||||
};
|
};
|
||||||
let sql = "CREATE TABLE IF NOT EXISTS something (key int, val array<int>)";
|
let sql = "CREATE TABLE IF NOT EXISTS something (name int, val array<int>)";
|
||||||
match dialects.one_statement_parses_to(
|
match dialects.one_statement_parses_to(
|
||||||
sql,
|
sql,
|
||||||
"CREATE TABLE IF NOT EXISTS something (key INT, val INT[])",
|
"CREATE TABLE IF NOT EXISTS something (name INT, val INT[])",
|
||||||
) {
|
) {
|
||||||
Statement::CreateTable {
|
Statement::CreateTable {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
|
@ -2106,7 +2106,7 @@ fn parse_create_table_hive_array() {
|
||||||
columns,
|
columns,
|
||||||
vec![
|
vec![
|
||||||
ColumnDef {
|
ColumnDef {
|
||||||
name: Ident::new("key"),
|
name: Ident::new("name"),
|
||||||
data_type: DataType::Int(None),
|
data_type: DataType::Int(None),
|
||||||
collation: None,
|
collation: None,
|
||||||
options: vec![],
|
options: vec![],
|
||||||
|
@ -2123,7 +2123,8 @@ fn parse_create_table_hive_array() {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = parse_sql_statements("CREATE TABLE IF NOT EXISTS something (key int, val array<int)");
|
let res =
|
||||||
|
parse_sql_statements("CREATE TABLE IF NOT EXISTS something (name int, val array<int)");
|
||||||
assert!(res
|
assert!(res
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
|
@ -1073,6 +1073,49 @@ fn parse_limit_my_sql_syntax() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_index_definition() {
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, INDEX (id))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, index USING BTREE (id))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX USING BTREE (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, KEY USING HASH (id))",
|
||||||
|
"CREATE TABLE tb (id INT, KEY USING HASH (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, key index (id))",
|
||||||
|
"CREATE TABLE tb (id INT, KEY index (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, INDEX 'index' (id))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX 'index' (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, INDEX index USING BTREE (id))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX index USING BTREE (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, INDEX index USING HASH (id))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX index USING HASH (id))",
|
||||||
|
);
|
||||||
|
|
||||||
|
mysql_and_generic().one_statement_parses_to(
|
||||||
|
"CREATE TABLE tb (id INT, INDEX (c1, c2, c3, c4,c5))",
|
||||||
|
"CREATE TABLE tb (id INT, INDEX (c1, c2, c3, c4, c5))",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn mysql() -> TestedDialects {
|
fn mysql() -> TestedDialects {
|
||||||
TestedDialects {
|
TestedDialects {
|
||||||
dialects: vec![Box::new(MySqlDialect {})],
|
dialects: vec![Box::new(MySqlDialect {})],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue