diff --git a/CHANGELOG.md b/CHANGELOG.md index 616f8774e..06b0220cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented - Use Token::EOF instead of Option (#195) - Make the units keyword following `INTERVAL '...'` optional (#184) - thanks @maxcountryman! - Generalize `DATE`/`TIME`/`TIMESTAMP` literals representation in the AST (`TypedString { data_type, value }`) and allow `DATE` and other keywords to be used as identifiers when not followed by a string (#187) - thanks @maxcountryman! +- Output DataType capitalized (`fmt::Display`) (#202) - thanks @Dandandan! ### Added - Support MSSQL `TOP () [ PERCENT ] [ WITH TIES ]` (#150) - thanks @alexkyllo! @@ -29,9 +30,11 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented - Support the string concatentation operator `||` (#178) - thanks @Dandandan! - Support bitwise AND (`&`), OR (`|`), XOR (`^`) (#181) - thanks @Dandandan! - Add serde support to AST structs and enums (#196) - thanks @panarch! +- Support `ALTER TABLE ADD COLUMN`, `RENAME COLUMN`, and `RENAME TO` (#203) - thanks @mashuai! ### Fixed - Report an error for unterminated string literals (#165) +- Make file format (`STORED AS`) case insensitive (#200) and don't allow quoting it (#201) - thanks @Dandandan! ## [0.5.0] - 2019-10-10 diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index d7503ba77..c1e66373b 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -23,15 +23,38 @@ use std::fmt; pub enum AlterTableOperation { /// `ADD ` AddConstraint(TableConstraint), + /// `ADD [ COLUMN ] ` + AddColumn { column_def: ColumnDef }, /// TODO: implement `DROP CONSTRAINT ` DropConstraint { name: Ident }, + /// `RENAME [ COLUMN ] TO ` + RenameColumn { + old_column_name: Ident, + new_column_name: Ident, + }, + /// `RENAME TO ` + RenameTable { table_name: Ident }, } impl fmt::Display for AlterTableOperation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c), + AlterTableOperation::AddColumn { column_def } => { + write!(f, "ADD COLUMN {}", column_def.to_string()) + } AlterTableOperation::DropConstraint { name } => write!(f, "DROP CONSTRAINT {}", name), + AlterTableOperation::RenameColumn { + old_column_name, + new_column_name, + } => write!( + f, + "RENAME COLUMN {} TO {}", + old_column_name, new_column_name + ), + AlterTableOperation::RenameTable { table_name } => { + write!(f, "RENAME TO {}", table_name) + } } } } diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index f5e75f74c..1d84fa5f5 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -340,6 +340,7 @@ define_keywords!( REGR_SXY, REGR_SYY, RELEASE, + RENAME, REPEATABLE, RESTRICT, RESULT, diff --git a/src/parser.rs b/src/parser.rs index 1763afb49..fb750afa0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1122,6 +1122,29 @@ impl Parser { }) } + fn parse_column_def(&mut self) -> Result { + let name = self.parse_identifier()?; + let data_type = self.parse_data_type()?; + let collation = if self.parse_keyword(Keyword::COLLATE) { + Some(self.parse_object_name()?) + } else { + None + }; + let mut options = vec![]; + loop { + match self.peek_token() { + Token::EOF | Token::Comma | Token::RParen => break, + _ => options.push(self.parse_column_option_def()?), + } + } + Ok(ColumnDef { + name, + data_type, + collation, + options, + }) + } + fn parse_columns(&mut self) -> Result<(Vec, Vec), ParserError> { let mut columns = vec![]; let mut constraints = vec![]; @@ -1132,28 +1155,9 @@ impl Parser { loop { if let Some(constraint) = self.parse_optional_table_constraint()? { constraints.push(constraint); - } else if let Token::Word(column_name) = self.peek_token() { - self.next_token(); - let data_type = self.parse_data_type()?; - let collation = if self.parse_keyword(Keyword::COLLATE) { - Some(self.parse_object_name()?) - } else { - None - }; - let mut options = vec![]; - loop { - match self.peek_token() { - Token::EOF | Token::Comma | Token::RParen => break, - _ => options.push(self.parse_column_option_def()?), - } - } - - columns.push(ColumnDef { - name: column_name.to_ident(), - data_type, - collation, - options, - }); + } else if let Token::Word(_) = self.peek_token() { + let column_def = self.parse_column_def()?; + columns.push(column_def); } else { return self.expected("column name or constraint definition", self.peek_token()); } @@ -1318,10 +1322,26 @@ impl Parser { if let Some(constraint) = self.parse_optional_table_constraint()? { AlterTableOperation::AddConstraint(constraint) } else { - return self.expected("a constraint in ALTER TABLE .. ADD", self.peek_token()); + let _ = self.parse_keyword(Keyword::COLUMN); + let column_def = self.parse_column_def()?; + AlterTableOperation::AddColumn { column_def } + } + } else if self.parse_keyword(Keyword::RENAME) { + if self.parse_keyword(Keyword::TO) { + let table_name = self.parse_identifier()?; + AlterTableOperation::RenameTable { table_name } + } else { + let _ = self.parse_keyword(Keyword::COLUMN); + let old_column_name = self.parse_identifier()?; + self.expect_keyword(Keyword::TO)?; + let new_column_name = self.parse_identifier()?; + AlterTableOperation::RenameColumn { + old_column_name, + new_column_name, + } } } else { - return self.expected("ADD after ALTER TABLE", self.peek_token()); + return self.expected("ADD or RENAME after ALTER TABLE", self.peek_token()); }; Ok(Statement::AlterTable { name: table_name, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index bff131334..15050c6fb 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1313,6 +1313,51 @@ fn parse_create_table_empty() { let _ = verified_stmt("CREATE TABLE t ()"); } +#[test] +fn parse_alter_table() { + let add_column = "ALTER TABLE tab ADD COLUMN foo TEXT"; + match verified_stmt(add_column) { + Statement::AlterTable { + name, + operation: AlterTableOperation::AddColumn { column_def }, + } => { + assert_eq!("tab", name.to_string()); + assert_eq!("foo", column_def.name.to_string()); + assert_eq!("TEXT", column_def.data_type.to_string()); + } + _ => unreachable!(), + }; + + let rename_table = "ALTER TABLE tab RENAME TO new_tab"; + match verified_stmt(rename_table) { + Statement::AlterTable { + name, + operation: AlterTableOperation::RenameTable { table_name }, + } => { + assert_eq!("tab", name.to_string()); + assert_eq!("new_tab", table_name.to_string()) + } + _ => unreachable!(), + }; + + let rename_column = "ALTER TABLE tab RENAME COLUMN foo TO new_foo"; + match verified_stmt(rename_column) { + Statement::AlterTable { + name, + operation: + AlterTableOperation::RenameColumn { + old_column_name, + new_column_name, + }, + } => { + assert_eq!("tab", name.to_string()); + assert_eq!(old_column_name.to_string(), "foo"); + assert_eq!(new_column_name.to_string(), "new_foo"); + } + _ => unreachable!(), + } +} + #[test] fn parse_alter_table_constraints() { check_one("CONSTRAINT address_pkey PRIMARY KEY (address_id)"); @@ -1347,9 +1392,7 @@ fn parse_alter_table_constraints() { fn parse_bad_constraint() { let res = parse_sql_statements("ALTER TABLE tab ADD"); assert_eq!( - ParserError::ParserError( - "Expected a constraint in ALTER TABLE .. ADD, found: EOF".to_string() - ), + ParserError::ParserError("Expected identifier, found: EOF".to_string()), res.unwrap_err() );