]> granicus.if.org Git - postgresql/commitdiff
Add IF NOT EXISTS processing to ALTER TABLE ADD COLUMN
authorAndrew Dunstan <andrew@dunslane.net>
Thu, 30 Jul 2015 01:30:00 +0000 (21:30 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 30 Jul 2015 01:30:00 +0000 (21:30 -0400)
Fabrízio de Royes Mello, reviewed by Payal Singh, Alvaro Herrera and
Michael Paquier.

doc/src/sgml/ref/alter_table.sgml
src/backend/commands/tablecmds.c
src/backend/parser/gram.y
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index 207fec1758e492865dd30b628066b5f0c0c6d2fc..1c1c18168e0c7656ac7bbcf4b833034424422c57 100644 (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>
 
-    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>
@@ -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>
index b459b1eaeca80a437deb0958cb8bace9d552fcd1..970abd4b50c9240f5c67c355604e3453218f6f49 100644 (file)
@@ -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,
@@ -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);
@@ -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);
@@ -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 */
 
@@ -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,
@@ -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)
@@ -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);
        }
@@ -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;
@@ -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);
@@ -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;
 }
 
 /*
index 8f053e47e82df8aebb228138691dc131f61805f8..6b02cecac14fe073249ac62dc2cb0749e8d70517 100644 (file)
@@ -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> */
@@ -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} */
index 8d124961ce614b93a4d915d4923ccac71b59c482..028d6ed85cdbe25e37231d156b3e927910e65daf 100644 (file)
@@ -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;
index 3feed7c232dca51a0479995395c03123dba04d9f..aefb5c9376850170d21dcfea06607f9a9553708e 100644 (file)
@@ -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;