diff --git a/CHANGELOG.md b/CHANGELOG.md index d552e942f..c65a68615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented - Support Snowflake's `FROM (table_name)` (#155) - thanks @eyalleshem! ### Added +- Support basic forms of `CREATE INDEX` and `DROP INDEX` (#167) - thanks @mashuai! - Support MSSQL `TOP () [ PERCENT ] [ WITH TIES ]` (#150) - thanks @alexkyllo! - Support MySQL `LIMIT row_count OFFSET offset` (not followed by `ROW` or `ROWS`) and remember which variant was parsed (#158) - thanks @mjibson! - Support PostgreSQL `CREATE TABLE IF NOT EXISTS table_name` (#163) - thanks @alex-dukhno! diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 98637e697..e7ae0c731 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -476,6 +476,15 @@ pub enum Statement { file_format: Option, location: Option, }, + /// CREATE INDEX + CreateIndex { + /// index name + name: ObjectName, + table_name: ObjectName, + columns: Vec, + unique: bool, + if_not_exists: bool, + }, /// ALTER TABLE AlterTable { /// Table name @@ -655,6 +664,28 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::CreateIndex { + name, + table_name, + columns, + unique, + if_not_exists, + } => { + write!( + f, + "CREATE{}INDEX{}{} ON {}({}", + if *unique { " UNIQUE " } else { " " }, + if *if_not_exists { + " IF NOT EXISTS " + } else { + " " + }, + name, + table_name, + display_separated(columns, ",") + )?; + write!(f, ");") + } Statement::AlterTable { name, operation } => { write!(f, "ALTER TABLE {} {}", name, operation) } @@ -819,6 +850,7 @@ impl FromStr for FileFormat { pub enum ObjectType { Table, View, + Index, } impl fmt::Display for ObjectType { @@ -826,6 +858,7 @@ impl fmt::Display for ObjectType { f.write_str(match self { ObjectType::Table => "TABLE", ObjectType::View => "VIEW", + ObjectType::Index => "INDEX", }) } } diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index 9795f2af3..7ab86de2e 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -200,6 +200,7 @@ define_keywords!( IDENTITY, IF, IN, + INDEX, INDICATOR, INNER, INOUT, diff --git a/src/parser.rs b/src/parser.rs index 7c6a401d5..dc8e6b7ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -855,13 +855,17 @@ impl Parser { pub fn parse_create(&mut self) -> Result { if self.parse_keyword("TABLE") { self.parse_create_table() + } else if self.parse_keyword("INDEX") { + self.parse_create_index(false) + } else if self.parse_keywords(vec!["UNIQUE", "INDEX"]) { + self.parse_create_index(true) } else if self.parse_keyword("MATERIALIZED") || self.parse_keyword("VIEW") { self.prev_token(); self.parse_create_view() } else if self.parse_keyword("EXTERNAL") { self.parse_create_external_table() } else { - self.expected("TABLE or VIEW after CREATE", self.peek_token()) + self.expected("TABLE, VIEW or INDEX after CREATE", self.peek_token()) } } @@ -912,8 +916,10 @@ impl Parser { ObjectType::Table } else if self.parse_keyword("VIEW") { ObjectType::View + } else if self.parse_keyword("INDEX") { + ObjectType::Index } else { - return self.expected("TABLE or VIEW after DROP", self.peek_token()); + return self.expected("TABLE, VIEW or INDEX after DROP", self.peek_token()); }; // Many dialects support the non standard `IF EXISTS` clause and allow // specifying multiple objects to delete in a single statement @@ -932,6 +938,21 @@ impl Parser { }) } + pub fn parse_create_index(&mut self, unique: bool) -> Result { + let if_not_exists = self.parse_keywords(vec!["IF", "NOT", "EXISTS"]); + let index_name = self.parse_object_name()?; + self.expect_keyword("ON")?; + let table_name = self.parse_object_name()?; + let columns = self.parse_parenthesized_column_list(Mandatory)?; + Ok(Statement::CreateIndex { + name: index_name, + table_name, + columns, + unique, + if_not_exists, + }) + } + pub fn parse_create_table(&mut self) -> Result { let if_not_exists = self.parse_keywords(vec!["IF", "NOT", "EXISTS"]); let table_name = self.parse_object_name()?; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 41ceeae54..147ebcd6a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2631,6 +2631,44 @@ fn ensure_multiple_dialects_are_tested() { let _ = parse_sql_statements("SELECT @foo"); } +#[test] +fn parse_create_index() { + let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test(name,age);"; + let ident_vec = vec![Ident::new("name"), Ident::new("age")]; + match verified_stmt(sql) { + Statement::CreateIndex { + name, + table_name, + columns, + unique, + if_not_exists, + } => { + assert_eq!("idx_name", name.to_string()); + assert_eq!("test", table_name.to_string()); + assert_eq!(ident_vec, columns); + assert_eq!(true, unique); + assert_eq!(true, if_not_exists) + } + _ => unreachable!(), + } +} +#[test] +fn parse_drop_index() { + let sql = "DROP INDEX idx_a"; + match verified_stmt(sql) { + Statement::Drop { + names, object_type, .. + } => { + assert_eq!( + vec!["idx_a"], + names.iter().map(ToString::to_string).collect::>() + ); + assert_eq!(ObjectType::Index, object_type); + } + _ => unreachable!(), + } +} + fn parse_sql_statements(sql: &str) -> Result, ParserError> { all_dialects().parse_sql_statements(sql) }