Commit 2cd40adb authored by Andrew Dunstan's avatar Andrew Dunstan

Add IF NOT EXISTS processing to ALTER TABLE ADD COLUMN

Fabrízio de Royes Mello, reviewed by Payal Singh, Alvaro Herrera and
Michael Paquier.
parent 632cd9f8
...@@ -36,7 +36,7 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> ...@@ -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> <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 ] 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 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> ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
...@@ -96,11 +96,13 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> ...@@ -96,11 +96,13 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><literal>ADD COLUMN</literal></term> <term><literal>ADD COLUMN [ IF NOT EXISTS ]</literal></term>
<listitem> <listitem>
<para> <para>
This form adds a new column to the table, using the same syntax as 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -328,8 +328,9 @@ static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recu ...@@ -328,8 +328,9 @@ static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recu
bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode); bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
Relation rel, ColumnDef *colDef, bool isOid, Relation rel, ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode); bool recurse, bool recursing, bool if_not_exists, LOCKMODE lockmode);
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);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); 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 add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse, static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
...@@ -2304,7 +2305,7 @@ renameatt_internal(Oid myrelid, ...@@ -2304,7 +2305,7 @@ renameatt_internal(Oid myrelid,
oldattname))); oldattname)));
/* new name should not already exist */ /* 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 */ /* apply the update */
namestrcpy(&(attform->attname), newattname); namestrcpy(&(attform->attname), newattname);
...@@ -3455,11 +3456,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -3455,11 +3456,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_AddColumnToView: /* add column via CREATE OR REPLACE case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */ * VIEW */
address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
false, false, false, lockmode); false, false, false, false, lockmode);
break; break;
case AT_AddColumnRecurse: case AT_AddColumnRecurse:
address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
false, true, false, lockmode); false, true, false, cmd->missing_ok, lockmode);
break; break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
...@@ -3572,14 +3573,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -3572,14 +3573,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (cmd->def != NULL) if (cmd->def != NULL)
address = address =
ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
true, false, false, lockmode); true, false, false, cmd->missing_ok, lockmode);
break; break;
case AT_AddOidsRecurse: /* SET WITH OIDS */ case AT_AddOidsRecurse: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */ /* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL) if (cmd->def != NULL)
address = address =
ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
true, true, false, lockmode); true, true, false, cmd->missing_ok, lockmode);
break; break;
case AT_DropOids: /* SET WITHOUT OIDS */ case AT_DropOids: /* SET WITHOUT OIDS */
...@@ -4677,7 +4678,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, ...@@ -4677,7 +4678,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
static ObjectAddress static ObjectAddress
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid, ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode) bool recurse, bool recursing, bool if_not_exists, LOCKMODE lockmode)
{ {
Oid myrelid = RelationGetRelid(rel); Oid myrelid = RelationGetRelid(rel);
Relation pgclass, Relation pgclass,
...@@ -4771,8 +4772,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -4771,8 +4772,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
elog(ERROR, "cache lookup failed for relation %u", myrelid); elog(ERROR, "cache lookup failed for relation %u", myrelid);
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
/* new name should not already exist */ /* skip if the name already exists and if_not_exists is true */
check_for_column_name_collision(rel, colDef->colname); 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 */ /* Determine the new attribute's number */
if (isOid) if (isOid)
...@@ -5002,7 +5009,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -5002,7 +5009,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Recurse to child; return value is ignored */ /* Recurse to child; return value is ignored */
ATExecAddColumn(wqueue, childtab, childrel, ATExecAddColumn(wqueue, childtab, childrel,
colDef, isOid, recurse, true, lockmode); colDef, isOid, recurse, true,
if_not_exists, lockmode);
heap_close(childrel, NoLock); heap_close(childrel, NoLock);
} }
...@@ -5013,10 +5021,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -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 * 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 static bool
check_for_column_name_collision(Relation rel, const char *colname) check_for_column_name_collision(Relation rel, const char *colname,
bool if_not_exists)
{ {
HeapTuple attTuple; HeapTuple attTuple;
int attnum; int attnum;
...@@ -5029,7 +5038,7 @@ check_for_column_name_collision(Relation rel, const char *colname) ...@@ -5029,7 +5038,7 @@ check_for_column_name_collision(Relation rel, const char *colname)
ObjectIdGetDatum(RelationGetRelid(rel)), ObjectIdGetDatum(RelationGetRelid(rel)),
PointerGetDatum(colname)); PointerGetDatum(colname));
if (!HeapTupleIsValid(attTuple)) if (!HeapTupleIsValid(attTuple))
return; return true;
attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum; attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
ReleaseSysCache(attTuple); ReleaseSysCache(attTuple);
...@@ -5045,10 +5054,23 @@ check_for_column_name_collision(Relation rel, const char *colname) ...@@ -5045,10 +5054,23 @@ check_for_column_name_collision(Relation rel, const char *colname)
errmsg("column name \"%s\" conflicts with a system column name", errmsg("column name \"%s\" conflicts with a system column name",
colname))); colname)));
else 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, ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN), (errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists", errmsg("column \"%s\" of relation \"%s\" already exists",
colname, RelationGetRelationName(rel)))); colname, RelationGetRelationName(rel))));
}
return true;
} }
/* /*
......
...@@ -1942,6 +1942,16 @@ alter_table_cmd: ...@@ -1942,6 +1942,16 @@ alter_table_cmd:
AlterTableCmd *n = makeNode(AlterTableCmd); AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn; n->subtype = AT_AddColumn;
n->def = $2; 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; $$ = (Node *)n;
} }
/* ALTER TABLE <name> ADD COLUMN <coldef> */ /* ALTER TABLE <name> ADD COLUMN <coldef> */
...@@ -1950,6 +1960,16 @@ alter_table_cmd: ...@@ -1950,6 +1960,16 @@ alter_table_cmd:
AlterTableCmd *n = makeNode(AlterTableCmd); AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_AddColumn; n->subtype = AT_AddColumn;
n->def = $3; 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; $$ = (Node *)n;
} }
/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */ /* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
......
...@@ -2605,3 +2605,92 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing ...@@ -2605,3 +2605,92 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3; DROP TABLE logged3;
DROP TABLE logged2; DROP TABLE logged2;
DROP TABLE logged1; 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;
...@@ -1724,3 +1724,34 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing ...@@ -1724,3 +1724,34 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3; DROP TABLE logged3;
DROP TABLE logged2; DROP TABLE logged2;
DROP TABLE logged1; 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment