Add IF NOT EXISTS processing to ALTER TABLE ADD COLUMN

Fabrízio de Royes Mello, reviewed by Payal Singh, Alvaro Herrera and
Michael Paquier.
This commit is contained in:
Andrew Dunstan 2015-07-29 21:30:00 -04:00
parent 632cd9f892
commit 2cd40adb85
5 changed files with 182 additions and 18 deletions

View File

@ -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>
<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>

View File

@ -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,
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,
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,
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,
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,
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,
/* 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,
/* /*
* 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)
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)
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;
} }
/* /*

View File

@ -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:
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} */

View File

@ -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;

View File

@ -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;