Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Nov 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 27 additions & 7 deletions src/ast/ddl.rs
Expand Up @@ -30,8 +30,15 @@ use crate::tokenizer::Token;
pub enum AlterTableOperation {
/// `ADD <table_constraint>`
AddConstraint(TableConstraint),
/// `ADD [ COLUMN ] <column_def>`
AddColumn { column_def: ColumnDef },
/// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
AddColumn {
/// `[COLUMN]`.
column_keyword: bool,
/// `[IF NOT EXISTS]`
if_not_exists: bool,
/// <column_def>.
column_def: ColumnDef,
},
/// `DROP CONSTRAINT [ IF EXISTS ] <name>`
DropConstraint {
if_exists: bool,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <name>`, 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
Expand Down
18 changes: 15 additions & 3 deletions src/parser.rs
Expand Up @@ -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) {
Expand Down Expand Up @@ -5841,7 +5854,6 @@ mod tests {

#[cfg(test)]
mod test_parse_data_type {

use crate::ast::{
CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, ObjectName, TimezoneInfo,
};
Expand Down
69 changes: 68 additions & 1 deletion tests/sqlparser_common.rs
Expand Up @@ -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());
Expand Down Expand Up @@ -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)");
Expand Down