Skip to content

Commit

Permalink
Add IF NOT EXISTS processing to ALTER TABLE ADD COLUMN
Browse files Browse the repository at this point in the history
Fabrízio de Royes Mello, reviewed by Payal Singh, Alvaro Herrera and
Michael Paquier.
  • Loading branch information
adunstan committed Jul 30, 2015
1 parent 632cd9f commit 2cd40ad
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 18 deletions.
8 changes: 5 additions & 3 deletions doc/src/sgml/ref/alter_table.sgml
Expand Up @@ -36,7 +36,7 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>

<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>

ADD [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column_name</replaceable> [ RESTRICT | CASCADE ]
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ] [ USING <replaceable class="PARAMETER">expression</replaceable> ]
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
Expand Down Expand Up @@ -96,11 +96,13 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>

<variablelist>
<varlistentry>
<term><literal>ADD COLUMN</literal></term>
<term><literal>ADD COLUMN [ IF NOT EXISTS ]</literal></term>
<listitem>
<para>
This form adds a new column to the table, using the same syntax as
<xref linkend="SQL-CREATETABLE">.
<xref linkend="SQL-CREATETABLE">. If <literal>IF NOT EXISTS</literal>
is specified and a column already exists with this name,
no error is thrown.
</para>
</listitem>
</varlistentry>
Expand Down
52 changes: 37 additions & 15 deletions src/backend/commands/tablecmds.c
Expand Up @@ -328,8 +328,9 @@ static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recu
bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
Relation rel, ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode);
static void check_for_column_name_collision(Relation rel, const char *colname);
bool recurse, bool recursing, bool if_not_exists, LOCKMODE lockmode);
static bool check_for_column_name_collision(Relation rel, const char *colname,
bool if_not_exists);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
Expand Down Expand Up @@ -2304,7 +2305,7 @@ renameatt_internal(Oid myrelid,
oldattname)));

/* new name should not already exist */
check_for_column_name_collision(targetrelation, newattname);
(void) check_for_column_name_collision(targetrelation, newattname, false);

/* apply the update */
namestrcpy(&(attform->attname), newattname);
Expand Down Expand Up @@ -3455,11 +3456,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
false, false, false, lockmode);
false, false, false, false, lockmode);
break;
case AT_AddColumnRecurse:
address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
false, true, false, lockmode);
false, true, false, cmd->missing_ok, lockmode);
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
Expand Down Expand Up @@ -3572,14 +3573,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (cmd->def != NULL)
address =
ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
true, false, false, lockmode);
true, false, false, cmd->missing_ok, lockmode);
break;
case AT_AddOidsRecurse: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
address =
ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
true, true, false, lockmode);
true, true, false, cmd->missing_ok, lockmode);
break;
case AT_DropOids: /* SET WITHOUT OIDS */

Expand Down Expand Up @@ -4677,7 +4678,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
static ObjectAddress
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode)
bool recurse, bool recursing, bool if_not_exists, LOCKMODE lockmode)
{
Oid myrelid = RelationGetRelid(rel);
Relation pgclass,
Expand Down Expand Up @@ -4771,8 +4772,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
elog(ERROR, "cache lookup failed for relation %u", myrelid);
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;

/* new name should not already exist */
check_for_column_name_collision(rel, colDef->colname);
/* skip if the name already exists and if_not_exists is true */
if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
{
heap_close(attrdesc, RowExclusiveLock);
heap_freetuple(reltup);
heap_close(pgclass, RowExclusiveLock);
return InvalidObjectAddress;
}

/* Determine the new attribute's number */
if (isOid)
Expand Down Expand Up @@ -5002,7 +5009,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

/* Recurse to child; return value is ignored */
ATExecAddColumn(wqueue, childtab, childrel,
colDef, isOid, recurse, true, lockmode);
colDef, isOid, recurse, true,
if_not_exists, lockmode);

heap_close(childrel, NoLock);
}
Expand All @@ -5013,10 +5021,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

/*
* If a new or renamed column will collide with the name of an existing
* column, error out.
* column and if_not_exists is false then error out, else do nothing.
*/
static void
check_for_column_name_collision(Relation rel, const char *colname)
static bool
check_for_column_name_collision(Relation rel, const char *colname,
bool if_not_exists)
{
HeapTuple attTuple;
int attnum;
Expand All @@ -5029,7 +5038,7 @@ check_for_column_name_collision(Relation rel, const char *colname)
ObjectIdGetDatum(RelationGetRelid(rel)),
PointerGetDatum(colname));
if (!HeapTupleIsValid(attTuple))
return;
return true;

attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
ReleaseSysCache(attTuple);
Expand All @@ -5045,10 +5054,23 @@ check_for_column_name_collision(Relation rel, const char *colname)
errmsg("column name \"%s\" conflicts with a system column name",
colname)));
else
{
if (if_not_exists)
{
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
colname, RelationGetRelationName(rel))));
return false;
}

ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists",
colname, RelationGetRelationName(rel))));
}

return true;
}

/*
Expand Down
20 changes: 20 additions & 0 deletions src/backend/parser/gram.y
Expand Up @@ -1942,6 +1942,16 @@ alter_table_cmd:
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
n->def = $2;
n->missing_ok = false;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
| ADD_P IF_P NOT EXISTS columnDef
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
n->def = $5;
n->missing_ok = true;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ADD COLUMN <coldef> */
Expand All @@ -1950,6 +1960,16 @@ alter_table_cmd:
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
n->def = $3;
n->missing_ok = false;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ADD COLUMN IF NOT EXISTS <coldef> */
| ADD_P COLUMN IF_P NOT EXISTS columnDef
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn;
n->def = $6;
n->missing_ok = true;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
Expand Down
89 changes: 89 additions & 0 deletions src/test/regress/expected/alter_table.out
Expand Up @@ -2605,3 +2605,92 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3;
DROP TABLE logged2;
DROP TABLE logged1;
-- test ADD COLUMN IF NOT EXISTS
CREATE TABLE test_add_column(c1 integer);
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |

ALTER TABLE test_add_column
ADD COLUMN c2 integer;
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |

ALTER TABLE test_add_column
ADD COLUMN c2 integer; -- fail because c2 already exists
ERROR: column "c2" of relation "test_add_column" already exists
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |

ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists
NOTICE: column "c2" of relation "test_add_column" already exists, skipping
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |

ALTER TABLE test_add_column
ADD COLUMN c2 integer, -- fail because c2 already exists
ADD COLUMN c3 integer;
ERROR: column "c2" of relation "test_add_column" already exists
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |

ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN c3 integer; -- fail because c3 already exists
NOTICE: column "c2" of relation "test_add_column" already exists, skipping
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |
c3 | integer |

ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
NOTICE: column "c2" of relation "test_add_column" already exists, skipping
NOTICE: column "c3" of relation "test_add_column" already exists, skipping
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |
c3 | integer |

ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
ADD COLUMN c4 integer;
NOTICE: column "c2" of relation "test_add_column" already exists, skipping
NOTICE: column "c3" of relation "test_add_column" already exists, skipping
\d test_add_column
Table "public.test_add_column"
Column | Type | Modifiers
--------+---------+-----------
c1 | integer |
c2 | integer |
c3 | integer |
c4 | integer |

DROP TABLE test_add_column;
31 changes: 31 additions & 0 deletions src/test/regress/sql/alter_table.sql
Expand Up @@ -1724,3 +1724,34 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3;
DROP TABLE logged2;
DROP TABLE logged1;

-- test ADD COLUMN IF NOT EXISTS
CREATE TABLE test_add_column(c1 integer);
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN c2 integer;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN c2 integer; -- fail because c2 already exists
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer; -- skipping because c2 already exists
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN c2 integer, -- fail because c2 already exists
ADD COLUMN c3 integer;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN c3 integer; -- fail because c3 already exists
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;

0 comments on commit 2cd40ad

Please sign in to comment.