From 165fe289776b3247bcd5087b166b2b231658b7dc Mon Sep 17 00:00:00 2001 From: AugustoFKL Date: Sun, 13 Nov 2022 12:37:23 -0300 Subject: [PATCH] feat: add INE for ALTER TABLE ADD COLUMN --- src/ast/ddl.rs | 34 +++++++++++++++---- src/parser.rs | 18 ++++++++-- tests/sqlparser_common.rs | 69 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 3203f0e20..3c2bc3427 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -30,8 +30,15 @@ use crate::tokenizer::Token; pub enum AlterTableOperation { /// `ADD ` AddConstraint(TableConstraint), - /// `ADD [ COLUMN ] ` - AddColumn { column_def: ColumnDef }, + /// `ADD [COLUMN] [IF NOT EXISTS] ` + AddColumn { + /// `[COLUMN]`. + column_keyword: bool, + /// `[IF NOT EXISTS]` + if_not_exists: bool, + /// . + column_def: ColumnDef, + }, /// `DROP CONSTRAINT [ IF EXISTS ] ` DropConstraint { if_exists: bool, @@ -100,8 +107,21 @@ impl fmt::Display for AlterTableOperation { ine = if *if_not_exists { " IF NOT EXISTS" } else { "" } ), AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c), - AlterTableOperation::AddColumn { column_def } => { - write!(f, "ADD COLUMN {}", column_def) + AlterTableOperation::AddColumn { + column_keyword, + if_not_exists, + column_def, + } => { + write!(f, "ADD")?; + if *column_keyword { + write!(f, " COLUMN")?; + } + if *if_not_exists { + write!(f, " IF NOT EXISTS")?; + } + write!(f, " {column_def}")?; + + Ok(()) } AlterTableOperation::AlterColumn { column_name, op } => { write!(f, "ALTER COLUMN {} {}", column_name, op) @@ -416,8 +436,8 @@ impl fmt::Display for KeyOrIndexDisplay { /// 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. +/// 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 @@ -466,7 +486,7 @@ impl fmt::Display for ColumnDef { /// they are allowed to be named. The specification distinguishes between /// constraints (NOT NULL, UNIQUE, PRIMARY KEY, and CHECK), which can be named /// and can appear in any order, and other options (DEFAULT, GENERATED), which -/// cannot be named and must appear in a fixed order. PostgreSQL, however, +/// cannot be named and must appear in a fixed order. `PostgreSQL`, however, /// allows preceding any option with `CONSTRAINT `, even those that are /// not really constraints, like NULL and DEFAULT. MSSQL is less permissive, /// allowing DEFAULT, UNIQUE, PRIMARY KEY and CHECK to be named, but not NULL or diff --git a/src/parser.rs b/src/parser.rs index f91223c2f..967cb25ca 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3175,9 +3175,22 @@ impl<'a> Parser<'a> { new_partitions: partitions, } } else { - let _ = self.parse_keyword(Keyword::COLUMN); + let column_keyword = self.parse_keyword(Keyword::COLUMN); + + let if_not_exists = if dialect_of!(self is PostgreSqlDialect | BigQueryDialect | GenericDialect) + { + self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]) + || if_not_exists + } else { + false + }; + let column_def = self.parse_column_def()?; - AlterTableOperation::AddColumn { column_def } + AlterTableOperation::AddColumn { + column_keyword, + if_not_exists, + column_def, + } } } } else if self.parse_keyword(Keyword::RENAME) { @@ -5841,7 +5854,6 @@ mod tests { #[cfg(test)] mod test_parse_data_type { - use crate::ast::{ CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo, }; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 83a7a4ca6..e1e986b1b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2528,8 +2528,15 @@ fn parse_alter_table() { match one_statement_parses_to(add_column, "ALTER TABLE tab ADD COLUMN foo TEXT") { Statement::AlterTable { name, - operation: AlterTableOperation::AddColumn { column_def }, + operation: + AlterTableOperation::AddColumn { + column_keyword, + if_not_exists, + column_def, + }, } => { + assert!(column_keyword); + assert!(!if_not_exists); assert_eq!("tab", name.to_string()); assert_eq!("foo", column_def.name.to_string()); assert_eq!("TEXT", column_def.data_type.to_string()); @@ -2567,6 +2574,66 @@ fn parse_alter_table() { } } +#[test] +fn parse_alter_table_add_column() { + match verified_stmt("ALTER TABLE tab ADD foo TEXT") { + Statement::AlterTable { + operation: AlterTableOperation::AddColumn { column_keyword, .. }, + .. + } => { + assert!(!column_keyword); + } + _ => unreachable!(), + }; + + match verified_stmt("ALTER TABLE tab ADD COLUMN foo TEXT") { + Statement::AlterTable { + operation: AlterTableOperation::AddColumn { column_keyword, .. }, + .. + } => { + assert!(column_keyword); + } + _ => unreachable!(), + }; +} + +#[test] +fn parse_alter_table_add_column_if_not_exists() { + let dialects = TestedDialects { + dialects: vec![ + Box::new(PostgreSqlDialect {}), + Box::new(BigQueryDialect {}), + Box::new(GenericDialect {}), + ], + }; + + match dialects.verified_stmt("ALTER TABLE tab ADD IF NOT EXISTS foo TEXT") { + Statement::AlterTable { + operation: AlterTableOperation::AddColumn { if_not_exists, .. }, + .. + } => { + assert!(if_not_exists); + } + _ => unreachable!(), + }; + + match dialects.verified_stmt("ALTER TABLE tab ADD COLUMN IF NOT EXISTS foo TEXT") { + Statement::AlterTable { + operation: + AlterTableOperation::AddColumn { + column_keyword, + if_not_exists, + .. + }, + .. + } => { + assert!(column_keyword); + assert!(if_not_exists); + } + _ => unreachable!(), + }; +} + #[test] fn parse_alter_table_constraints() { check_one("CONSTRAINT address_pkey PRIMARY KEY (address_id)");