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>,
|
||||
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 {
|
||||
|
@ -290,6 +308,48 @@ impl fmt::Display for TableConstraint {
|
|||
TableConstraint::Check { 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,
|
||||
};
|
||||
pub use self::ddl::{
|
||||
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef,
|
||||
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, IndexType,
|
||||
ReferentialAction, TableConstraint,
|
||||
};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
|
|
|
@ -105,6 +105,7 @@ define_keywords!(
|
|||
BLOB,
|
||||
BOOLEAN,
|
||||
BOTH,
|
||||
BTREE,
|
||||
BY,
|
||||
BYPASSRLS,
|
||||
BYTEA,
|
||||
|
@ -265,6 +266,7 @@ define_keywords!(
|
|||
GROUP,
|
||||
GROUPING,
|
||||
GROUPS,
|
||||
HASH,
|
||||
HAVING,
|
||||
HEADER,
|
||||
HIVEVAR,
|
||||
|
|
131
src/parser.rs
131
src/parser.rs
|
@ -3003,6 +3003,31 @@ impl<'a> Parser<'a> {
|
|||
self.expect_token(&Token::RParen)?;
|
||||
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 => {
|
||||
if name.is_some() {
|
||||
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> {
|
||||
let name = self.parse_identifier()?;
|
||||
self.expect_token(&Token::Eq)?;
|
||||
|
@ -5779,4 +5814,100 @@ mod tests {
|
|||
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 {
|
||||
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(
|
||||
sql,
|
||||
"CREATE TABLE IF NOT EXISTS something (key INT, val INT[])",
|
||||
"CREATE TABLE IF NOT EXISTS something (name INT, val INT[])",
|
||||
) {
|
||||
Statement::CreateTable {
|
||||
if_not_exists,
|
||||
|
@ -2106,7 +2106,7 @@ fn parse_create_table_hive_array() {
|
|||
columns,
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("key"),
|
||||
name: Ident::new("name"),
|
||||
data_type: DataType::Int(None),
|
||||
collation: None,
|
||||
options: vec![],
|
||||
|
@ -2123,7 +2123,8 @@ fn parse_create_table_hive_array() {
|
|||
_ => 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
|
||||
.unwrap_err()
|
||||
.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 {
|
||||
TestedDialects {
|
||||
dialects: vec![Box::new(MySqlDialect {})],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue