Support renaming an existing value of an enum type.
authorTom Lane <[email protected]>
Wed, 7 Sep 2016 20:11:56 +0000 (16:11 -0400)
committerTom Lane <[email protected]>
Wed, 7 Sep 2016 20:11:56 +0000 (16:11 -0400)
Not much to be said about this patch: it does what it says on the tin.

In passing, rename AlterEnumStmt.skipIfExists to skipIfNewValExists
to clarify what it actually does.  In the discussion of this patch
we considered supporting other similar options, such as IF EXISTS
on the type as a whole or IF NOT EXISTS on the target name.  This
patch doesn't actually add any such feature, but it might happen later.

Dagfinn Ilmari MannsÃ¥ker, reviewed by Emre Hasegeli

Discussion: <CAO=2mx6uvgPaPDf-rHqG8=1MZnGyVDMQeh8zS4euRyyg4D35OQ@mail.gmail.com>

doc/src/sgml/ref/alter_type.sgml
src/backend/catalog/pg_enum.c
src/backend/commands/typecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/include/catalog/pg_enum.h
src/include/nodes/parsenodes.h
src/test/regress/expected/enum.out
src/test/regress/sql/enum.sql

index aec73f62852683b599c5d224498df5a1f860a1a1..fdb4f3367d576071a1315d600fa75a6c22485590 100644 (file)
@@ -28,7 +28,8 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO { <replace
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable> [ CASCADE | RESTRICT ]
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
 ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
-ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT EXISTS ] <replaceable class="PARAMETER">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="PARAMETER">existing_enum_value</replaceable> ]
+ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT EXISTS ] <replaceable class="PARAMETER">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="PARAMETER">neighbor_enum_value</replaceable> ]
+ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME VALUE <replaceable class="PARAMETER">existing_enum_value</replaceable> TO <replaceable class="PARAMETER">new_enum_value</replaceable>
 
 <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
 
@@ -124,21 +125,13 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT
    </varlistentry>
 
    <varlistentry>
-    <term><literal>CASCADE</literal></term>
+    <term><literal>RENAME VALUE</literal></term>
     <listitem>
      <para>
-      Automatically propagate the operation to typed tables of the
-      type being altered, and their descendants.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term><literal>RESTRICT</literal></term>
-    <listitem>
-     <para>
-      Refuse the operation if the type being altered is the type of a
-      typed table.  This is the default.
+      This form renames a value of an enum type.
+      The value's place in the enum's ordering is not affected.
+      An error will occur if the specified value is not present or the new
+      name is already present.
      </para>
     </listitem>
    </varlistentry>
@@ -241,14 +234,15 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT
       <term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
       <listitem>
        <para>
-        The new value to be added to an enum type's list of values.
+        The new value to be added to an enum type's list of values,
+        or the new name to be given to an existing value.
         Like all enum literals, it needs to be quoted.
        </para>
       </listitem>
      </varlistentry>
 
      <varlistentry>
-      <term><replaceable class="PARAMETER">existing_enum_value</replaceable></term>
+      <term><replaceable class="PARAMETER">neighbor_enum_value</replaceable></term>
       <listitem>
        <para>
         The existing enum value that the new value should be added immediately
@@ -258,6 +252,36 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">existing_enum_value</replaceable></term>
+      <listitem>
+       <para>
+        The existing enum value that should be renamed.
+        Like all enum literals, it needs to be quoted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>CASCADE</literal></term>
+      <listitem>
+       <para>
+        Automatically propagate the operation to typed tables of the
+        type being altered, and their descendants.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>RESTRICT</literal></term>
+      <listitem>
+       <para>
+        Refuse the operation if the type being altered is the type of a
+        typed table.  This is the default.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
   </refsect1>
@@ -270,6 +294,8 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE [ IF NOT
    an enum type) is executed inside a transaction block, the new value cannot
    be used until after the transaction has been committed, except in the case
    that the enum type itself was created earlier in the same transaction.
+   Likewise, when a pre-existing enum value is renamed, the transaction must
+   be committed before the renamed value can be used.
   </para>
 
   <para>
@@ -323,7 +349,15 @@ ALTER TYPE compfoo ADD ATTRIBUTE f3 int;
    To add a new value to an enum type in a particular sort position:
 <programlisting>
 ALTER TYPE colors ADD VALUE 'orange' AFTER 'red';
-</programlisting></para>
+</programlisting>
+  </para>
+
+  <para>
+   To rename an enum value:
+<programlisting>
+ALTER TYPE colors RENAME VALUE 'purple' TO 'mauve';
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
index c66f9632c29a3c36843d03df25a0059145a6841c..1f0ffcfa1591b8d9f3d6ee215c6e8dd3e356b020 100644 (file)
@@ -465,6 +465,91 @@ restart:
 }
 
 
+/*
+ * RenameEnumLabel
+ *             Rename a label in an enum set.
+ */
+void
+RenameEnumLabel(Oid enumTypeOid,
+                               const char *oldVal,
+                               const char *newVal)
+{
+       Relation        pg_enum;
+       HeapTuple       enum_tup;
+       Form_pg_enum en;
+       CatCList   *list;
+       int                     nelems;
+       HeapTuple       old_tup;
+       bool            found_new;
+       int                     i;
+
+       /* check length of new label is ok */
+       if (strlen(newVal) > (NAMEDATALEN - 1))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_NAME),
+                                errmsg("invalid enum label \"%s\"", newVal),
+                                errdetail("Labels must be %d characters or less.",
+                                                  NAMEDATALEN - 1)));
+
+       /*
+        * Acquire a lock on the enum type, which we won't release until commit.
+        * This ensures that two backends aren't concurrently modifying the same
+        * enum type.  Since we are not changing the type's sort order, this is
+        * probably not really necessary, but there seems no reason not to take
+        * the lock to be sure.
+        */
+       LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
+
+       pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
+
+       /* Get the list of existing members of the enum */
+       list = SearchSysCacheList1(ENUMTYPOIDNAME,
+                                                          ObjectIdGetDatum(enumTypeOid));
+       nelems = list->n_members;
+
+       /*
+        * Locate the element to rename and check if the new label is already in
+        * use.  (The unique index on pg_enum would catch that anyway, but we
+        * prefer a friendlier error message.)
+        */
+       old_tup = NULL;
+       found_new = false;
+       for (i = 0; i < nelems; i++)
+       {
+               enum_tup = &(list->members[i]->tuple);
+               en = (Form_pg_enum) GETSTRUCT(enum_tup);
+               if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
+                       old_tup = enum_tup;
+               if (strcmp(NameStr(en->enumlabel), newVal) == 0)
+                       found_new = true;
+       }
+       if (!old_tup)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("\"%s\" is not an existing enum label",
+                                               oldVal)));
+       if (found_new)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("enum label \"%s\" already exists",
+                                               newVal)));
+
+       /* OK, make a writable copy of old tuple */
+       enum_tup = heap_copytuple(old_tup);
+       en = (Form_pg_enum) GETSTRUCT(enum_tup);
+
+       ReleaseCatCacheList(list);
+
+       /* Update the pg_enum entry */
+       namestrcpy(&en->enumlabel, newVal);
+       simple_heap_update(pg_enum, &enum_tup->t_self, enum_tup);
+       CatalogUpdateIndexes(pg_enum, enum_tup);
+       heap_freetuple(enum_tup);
+
+       heap_close(pg_enum, RowExclusiveLock);
+}
+
+
 /*
  * RenumberEnumType
  *             Renumber existing enum elements to have sort positions 1..n.
index 6cc7106467da6f48334c25ff0790d675888dd9e2..41fd2dae7f8c30d8803f48d497ecdc7aacae4f87 100644 (file)
@@ -1241,17 +1241,25 @@ AlterEnum(AlterEnumStmt *stmt)
        /* Check it's an enum and check user has permission to ALTER the enum */
        checkEnumOwner(tup);
 
-       /* Add the new label */
-       AddEnumLabel(enum_type_oid, stmt->newVal,
-                                stmt->newValNeighbor, stmt->newValIsAfter,
-                                stmt->skipIfExists);
+       ReleaseSysCache(tup);
+
+       if (stmt->oldVal)
+       {
+               /* Rename an existing label */
+               RenameEnumLabel(enum_type_oid, stmt->oldVal, stmt->newVal);
+       }
+       else
+       {
+               /* Add a new label */
+               AddEnumLabel(enum_type_oid, stmt->newVal,
+                                        stmt->newValNeighbor, stmt->newValIsAfter,
+                                        stmt->skipIfNewValExists);
+       }
 
        InvokeObjectPostAlterHook(TypeRelationId, enum_type_oid, 0);
 
        ObjectAddressSet(address, TypeRelationId, enum_type_oid);
 
-       ReleaseSysCache(tup);
-
        return address;
 }
 
index be2207e3188fdd03b204ec1f5152e8045f98366e..4f39dad66b4ec5e483cd8d08692f5aa42834dd1a 100644 (file)
@@ -3375,10 +3375,11 @@ _copyAlterEnumStmt(const AlterEnumStmt *from)
        AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
 
        COPY_NODE_FIELD(typeName);
+       COPY_STRING_FIELD(oldVal);
        COPY_STRING_FIELD(newVal);
        COPY_STRING_FIELD(newValNeighbor);
        COPY_SCALAR_FIELD(newValIsAfter);
-       COPY_SCALAR_FIELD(skipIfExists);
+       COPY_SCALAR_FIELD(skipIfNewValExists);
 
        return newnode;
 }
index c4ec4077a60771139ac80f86b00bc4da0a2fbb97..4800165a919f3cbe447dfb199030354d76497ad8 100644 (file)
@@ -1465,10 +1465,11 @@ static bool
 _equalAlterEnumStmt(const AlterEnumStmt *a, const AlterEnumStmt *b)
 {
        COMPARE_NODE_FIELD(typeName);
+       COMPARE_STRING_FIELD(oldVal);
        COMPARE_STRING_FIELD(newVal);
        COMPARE_STRING_FIELD(newValNeighbor);
        COMPARE_SCALAR_FIELD(newValIsAfter);
-       COMPARE_SCALAR_FIELD(skipIfExists);
+       COMPARE_SCALAR_FIELD(skipIfNewValExists);
 
        return true;
 }
index b69a77a588ffbaf34661925643cafe0329973da5..1526c73a1c52c103d31b24203899ff2fda0d16ff 100644 (file)
@@ -5257,30 +5257,44 @@ AlterEnumStmt:
                        {
                                AlterEnumStmt *n = makeNode(AlterEnumStmt);
                                n->typeName = $3;
+                               n->oldVal = NULL;
                                n->newVal = $7;
                                n->newValNeighbor = NULL;
                                n->newValIsAfter = true;
-                               n->skipIfExists = $6;
+                               n->skipIfNewValExists = $6;
                                $$ = (Node *) n;
                        }
                 | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst BEFORE Sconst
                        {
                                AlterEnumStmt *n = makeNode(AlterEnumStmt);
                                n->typeName = $3;
+                               n->oldVal = NULL;
                                n->newVal = $7;
                                n->newValNeighbor = $9;
                                n->newValIsAfter = false;
-                               n->skipIfExists = $6;
+                               n->skipIfNewValExists = $6;
                                $$ = (Node *) n;
                        }
                 | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst AFTER Sconst
                        {
                                AlterEnumStmt *n = makeNode(AlterEnumStmt);
                                n->typeName = $3;
+                               n->oldVal = NULL;
                                n->newVal = $7;
                                n->newValNeighbor = $9;
                                n->newValIsAfter = true;
-                               n->skipIfExists = $6;
+                               n->skipIfNewValExists = $6;
+                               $$ = (Node *) n;
+                       }
+                | ALTER TYPE_P any_name RENAME VALUE_P Sconst TO Sconst
+                       {
+                               AlterEnumStmt *n = makeNode(AlterEnumStmt);
+                               n->typeName = $3;
+                               n->oldVal = $6;
+                               n->newVal = $8;
+                               n->newValNeighbor = NULL;
+                               n->newValIsAfter = false;
+                               n->skipIfNewValExists = false;
                                $$ = (Node *) n;
                        }
                 ;
index dd32443b91e3058b240108b8293587a711c07afa..901d3adbb9a2ec9ef50cf6c9b104f64d10608f36 100644 (file)
@@ -67,5 +67,7 @@ extern void EnumValuesDelete(Oid enumTypeOid);
 extern void AddEnumLabel(Oid enumTypeOid, const char *newVal,
                         const char *neighbor, bool newValIsAfter,
                         bool skipIfExists);
+extern void RenameEnumLabel(Oid enumTypeOid,
+                               const char *oldVal, const char *newVal);
 
 #endif   /* PG_ENUM_H */
index 3716c2eef961d668c24fd827a19e2adb522038fb..8d3dcf4d4c17b3f50b43063040a96e56e9b2723b 100644 (file)
@@ -2708,10 +2708,11 @@ typedef struct AlterEnumStmt
 {
        NodeTag         type;
        List       *typeName;           /* qualified name (list of Value strings) */
+       char       *oldVal;                     /* old enum value's name, if renaming */
        char       *newVal;                     /* new enum value's name */
        char       *newValNeighbor; /* neighboring enum value, if specified */
        bool            newValIsAfter;  /* place new enum value after neighbor? */
-       bool            skipIfExists;   /* no error if label already exists */
+       bool            skipIfNewValExists;             /* no error if new already exists? */
 } AlterEnumStmt;
 
 /* ----------------------
index d4a45a306bc2f94bf5d31bd950fbb50742165faf..514d1d01a10caec3e27974d6f3cb834171bfbe82 100644 (file)
@@ -556,6 +556,28 @@ CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent);
 ERROR:  foreign key constraint "enumtest_bogus_child_parent_fkey" cannot be implemented
 DETAIL:  Key columns "parent" and "id" are of incompatible types: bogus and rainbow.
 DROP TYPE bogus;
+-- check renaming a value
+ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson';
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'rainbow'::regtype
+ORDER BY 2;
+ enumlabel | enumsortorder 
+-----------+---------------
+ crimson   |             1
+ orange    |             2
+ yellow    |             3
+ green     |             4
+ blue      |             5
+ purple    |             6
+(6 rows)
+
+-- check that renaming a non-existent value fails
+ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson';
+ERROR:  "red" is not an existing enum label
+-- check that renaming to an existent value fails
+ALTER TYPE rainbow RENAME VALUE 'blue' TO 'green';
+ERROR:  enum label "green" already exists
 --
 -- check transactional behaviour of ALTER TYPE ... ADD VALUE
 --
index d25e8dedb6cec03df92d1fad3bec75455dcc78c9..d7e87143a010a10de4ded456c711dedf82dc1377 100644 (file)
@@ -257,6 +257,17 @@ CREATE TYPE bogus AS ENUM('good', 'bad', 'ugly');
 CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent);
 DROP TYPE bogus;
 
+-- check renaming a value
+ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson';
+SELECT enumlabel, enumsortorder
+FROM pg_enum
+WHERE enumtypid = 'rainbow'::regtype
+ORDER BY 2;
+-- check that renaming a non-existent value fails
+ALTER TYPE rainbow RENAME VALUE 'red' TO 'crimson';
+-- check that renaming to an existent value fails
+ALTER TYPE rainbow RENAME VALUE 'blue' TO 'green';
+
 --
 -- check transactional behaviour of ALTER TYPE ... ADD VALUE
 --