Support for IF NOT EXISTS in ALTER TABLE ADD COLUMN (#707)

This commit is contained in:
Augusto Fotino 2022-11-29 17:37:14 -03:00 committed by GitHub
parent bae682255d
commit 886875f3bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 11 deletions

View file

@ -30,8 +30,15 @@ use crate::tokenizer::Token;
pub enum AlterTableOperation { pub enum AlterTableOperation {
/// `ADD <table_constraint>` /// `ADD <table_constraint>`
AddConstraint(TableConstraint), AddConstraint(TableConstraint),
/// `ADD [ COLUMN ] <column_def>` /// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
AddColumn { column_def: ColumnDef }, AddColumn {
/// `[COLUMN]`.
column_keyword: bool,
/// `[IF NOT EXISTS]`
if_not_exists: bool,
/// <column_def>.
column_def: ColumnDef,
},
/// `DROP CONSTRAINT [ IF EXISTS ] <name>` /// `DROP CONSTRAINT [ IF EXISTS ] <name>`
DropConstraint { DropConstraint {
if_exists: bool, if_exists: bool,
@ -100,8 +107,21 @@ impl fmt::Display for AlterTableOperation {
ine = if *if_not_exists { " IF NOT EXISTS" } else { "" } ine = if *if_not_exists { " IF NOT EXISTS" } else { "" }
), ),
AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c), AlterTableOperation::AddConstraint(c) => write!(f, "ADD {}", c),
AlterTableOperation::AddColumn { column_def } => { AlterTableOperation::AddColumn {
write!(f, "ADD COLUMN {}", column_def) 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 } => { AlterTableOperation::AlterColumn { column_name, op } => {
write!(f, "ALTER COLUMN {} {}", 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. /// Indexing method used by that index.
/// ///
/// This structure isn't present on ANSI, but is found at least in [MySQL CREATE TABLE][1], /// 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. /// [`MySQL` CREATE INDEX][2], and [Postgresql CREATE INDEX][3] statements.
/// ///
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html /// [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 /// [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 /// they are allowed to be named. The specification distinguishes between
/// constraints (NOT NULL, UNIQUE, PRIMARY KEY, and CHECK), which can be named /// constraints (NOT NULL, UNIQUE, PRIMARY KEY, and CHECK), which can be named
/// and can appear in any order, and other options (DEFAULT, GENERATED), which /// 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 <name>`, even those that are /// allows preceding any option with `CONSTRAINT <name>`, even those that are
/// not really constraints, like NULL and DEFAULT. MSSQL is less permissive, /// 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 /// allowing DEFAULT, UNIQUE, PRIMARY KEY and CHECK to be named, but not NULL or

View file

@ -3205,9 +3205,22 @@ impl<'a> Parser<'a> {
new_partitions: partitions, new_partitions: partitions,
} }
} else { } 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()?; 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) { } else if self.parse_keyword(Keyword::RENAME) {
@ -5858,7 +5871,6 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod test_parse_data_type { mod test_parse_data_type {
use crate::ast::{ use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
}; };

View file

@ -2528,8 +2528,15 @@ fn parse_alter_table() {
match one_statement_parses_to(add_column, "ALTER TABLE tab ADD COLUMN foo TEXT") { match one_statement_parses_to(add_column, "ALTER TABLE tab ADD COLUMN foo TEXT") {
Statement::AlterTable { Statement::AlterTable {
name, 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!("tab", name.to_string());
assert_eq!("foo", column_def.name.to_string()); assert_eq!("foo", column_def.name.to_string());
assert_eq!("TEXT", column_def.data_type.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] #[test]
fn parse_alter_table_constraints() { fn parse_alter_table_constraints() {
check_one("CONSTRAINT address_pkey PRIMARY KEY (address_id)"); check_one("CONSTRAINT address_pkey PRIMARY KEY (address_id)");