Allow IF NOT EXISTS when add a new enum label.

If the label is already in the enum the statement becomes a no-op.
This will reduce the pain that comes from our not allowing this
operation inside a transaction block.

Andrew Dunstan, reviewed by Tom Lane and Magnus Hagander.
This commit is contained in:
Andrew Dunstan 2012-09-22 12:53:31 -04:00
parent 11e131854f
commit 6d12b68cd7
10 changed files with 89 additions and 14 deletions

View File

@ -28,7 +28,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceab
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable> ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable>
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable> [ CASCADE | RESTRICT ] ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable> [ CASCADE | RESTRICT ]
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</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 <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">existing_enum_value</replaceable> ]
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase> <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
@ -106,7 +106,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE <replacea
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><literal>ADD VALUE [ BEFORE | AFTER ]</literal></term> <term><literal>ADD VALUE [ IF NOT EXISTS ] [ BEFORE | AFTER ]</literal></term>
<listitem> <listitem>
<para> <para>
This form adds a new value to an enum type. If the new value's place in This form adds a new value to an enum type. If the new value's place in
@ -114,6 +114,11 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD VALUE <replacea
<literal>AFTER</literal>, then the new item is placed at the end of the <literal>AFTER</literal>, then the new item is placed at the end of the
list of values. list of values.
</para> </para>
<para>
If <literal>IF NOT EXISTS</literal is used, it is not an error if the
type already contains the new value, and no action is taken. Otherwise,
an error will occur if the new value is already present.
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -179,7 +179,8 @@ void
AddEnumLabel(Oid enumTypeOid, AddEnumLabel(Oid enumTypeOid,
const char *newVal, const char *newVal,
const char *neighbor, const char *neighbor,
bool newValIsAfter) bool newValIsAfter,
bool skipIfExists)
{ {
Relation pg_enum; Relation pg_enum;
Oid newOid; Oid newOid;
@ -211,6 +212,21 @@ AddEnumLabel(Oid enumTypeOid,
*/ */
LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock); LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
/* Do the "IF NOT EXISTS" test if specified */
if (skipIfExists)
{
HeapTuple tup;
tup = SearchSysCache2(ENUMTYPOIDNAME,
ObjectIdGetDatum(enumTypeOid),
CStringGetDatum(newVal));
if (HeapTupleIsValid(tup))
{
ReleaseSysCache(tup);
return;
}
}
pg_enum = heap_open(EnumRelationId, RowExclusiveLock); pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
/* If we have to renumber the existing members, we restart from here */ /* If we have to renumber the existing members, we restart from here */

View File

@ -1188,7 +1188,8 @@ AlterEnum(AlterEnumStmt *stmt)
/* Add the new label */ /* Add the new label */
AddEnumLabel(enum_type_oid, stmt->newVal, AddEnumLabel(enum_type_oid, stmt->newVal,
stmt->newValNeighbor, stmt->newValIsAfter); stmt->newValNeighbor, stmt->newValIsAfter,
stmt->skipIfExists);
ReleaseSysCache(tup); ReleaseSysCache(tup);
} }

View File

@ -3055,6 +3055,7 @@ _copyAlterEnumStmt(const AlterEnumStmt *from)
COPY_STRING_FIELD(newVal); COPY_STRING_FIELD(newVal);
COPY_STRING_FIELD(newValNeighbor); COPY_STRING_FIELD(newValNeighbor);
COPY_SCALAR_FIELD(newValIsAfter); COPY_SCALAR_FIELD(newValIsAfter);
COPY_SCALAR_FIELD(skipIfExists);
return newnode; return newnode;
} }

View File

@ -1439,6 +1439,7 @@ _equalAlterEnumStmt(const AlterEnumStmt *a, const AlterEnumStmt *b)
COMPARE_STRING_FIELD(newVal); COMPARE_STRING_FIELD(newVal);
COMPARE_STRING_FIELD(newValNeighbor); COMPARE_STRING_FIELD(newValNeighbor);
COMPARE_SCALAR_FIELD(newValIsAfter); COMPARE_SCALAR_FIELD(newValIsAfter);
COMPARE_SCALAR_FIELD(skipIfExists);
return true; return true;
} }

View File

@ -470,7 +470,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <windef> window_definition over_clause window_specification %type <windef> window_definition over_clause window_specification
opt_frame_clause frame_extent frame_bound opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name %type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
/* /*
* Non-keyword token types. These are hard-wired into the "flex" lexer. * Non-keyword token types. These are hard-wired into the "flex" lexer.
@ -4618,35 +4618,42 @@ enum_val_list: Sconst
*****************************************************************************/ *****************************************************************************/
AlterEnumStmt: AlterEnumStmt:
ALTER TYPE_P any_name ADD_P VALUE_P Sconst ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst
{ {
AlterEnumStmt *n = makeNode(AlterEnumStmt); AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3; n->typeName = $3;
n->newVal = $6; n->newVal = $7;
n->newValNeighbor = NULL; n->newValNeighbor = NULL;
n->newValIsAfter = true; n->newValIsAfter = true;
n->skipIfExists = $6;
$$ = (Node *) n; $$ = (Node *) n;
} }
| ALTER TYPE_P any_name ADD_P VALUE_P Sconst BEFORE Sconst | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst BEFORE Sconst
{ {
AlterEnumStmt *n = makeNode(AlterEnumStmt); AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3; n->typeName = $3;
n->newVal = $6; n->newVal = $7;
n->newValNeighbor = $8; n->newValNeighbor = $9;
n->newValIsAfter = false; n->newValIsAfter = false;
n->skipIfExists = $6;
$$ = (Node *) n; $$ = (Node *) n;
} }
| ALTER TYPE_P any_name ADD_P VALUE_P Sconst AFTER Sconst | ALTER TYPE_P any_name ADD_P VALUE_P opt_if_not_exists Sconst AFTER Sconst
{ {
AlterEnumStmt *n = makeNode(AlterEnumStmt); AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3; n->typeName = $3;
n->newVal = $6; n->newVal = $7;
n->newValNeighbor = $8; n->newValNeighbor = $9;
n->newValIsAfter = true; n->newValIsAfter = true;
n->skipIfExists = $6;
$$ = (Node *) n; $$ = (Node *) n;
} }
; ;
opt_if_not_exists: IF_P NOT EXISTS { $$ = true; }
| /* empty */ { $$ = false; }
;
/***************************************************************************** /*****************************************************************************
* *

View File

@ -65,6 +65,7 @@ typedef FormData_pg_enum *Form_pg_enum;
extern void EnumValuesCreate(Oid enumTypeOid, List *vals); extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
extern void EnumValuesDelete(Oid enumTypeOid); extern void EnumValuesDelete(Oid enumTypeOid);
extern void AddEnumLabel(Oid enumTypeOid, const char *newVal, extern void AddEnumLabel(Oid enumTypeOid, const char *newVal,
const char *neighbor, bool newValIsAfter); const char *neighbor, bool newValIsAfter,
bool skipIfExists);
#endif /* PG_ENUM_H */ #endif /* PG_ENUM_H */

View File

@ -2306,6 +2306,7 @@ typedef struct AlterEnumStmt
char *newVal; /* new enum value's name */ char *newVal; /* new enum value's name */
char *newValNeighbor; /* neighboring enum value, if specified */ char *newValNeighbor; /* neighboring enum value, if specified */
bool newValIsAfter; /* place new enum value after neighbor? */ bool newValIsAfter; /* place new enum value after neighbor? */
bool skipIfExists; /* ignore statement if label already exists */
} AlterEnumStmt; } AlterEnumStmt;
/* ---------------------- /* ----------------------

View File

@ -95,6 +95,28 @@ ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutopl
DETAIL: Labels must be 63 characters or less. DETAIL: Labels must be 63 characters or less.
ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus'; ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus';
ERROR: "zeus" is not an existing enum label ERROR: "zeus" is not an existing enum label
-- if not exists tests
-- existing value gives error
-- We can't do this test because the error contains the
-- offending Oid value, which is unpredictable.
-- ALTER TYPE planets ADD VALUE 'mercury';
-- unless IF NOT EXISTS is specified
ALTER TYPE planets ADD VALUE IF NOT EXISTS 'mercury';
-- should be neptune, not mercury
SELECT enum_last(NULL::planets);
enum_last
-----------
neptune
(1 row)
ALTER TYPE planets ADD VALUE IF NOT EXISTS 'pluto';
-- should be pluto, i.e. the new value
SELECT enum_last(NULL::planets);
enum_last
-----------
pluto
(1 row)
-- --
-- Test inserting so many values that we have to renumber -- Test inserting so many values that we have to renumber
-- --

View File

@ -54,6 +54,26 @@ ALTER TYPE planets ADD VALUE
ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus'; ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus';
-- if not exists tests
-- existing value gives error
-- We can't do this test because the error contains the
-- offending Oid value, which is unpredictable.
-- ALTER TYPE planets ADD VALUE 'mercury';
-- unless IF NOT EXISTS is specified
ALTER TYPE planets ADD VALUE IF NOT EXISTS 'mercury';
-- should be neptune, not mercury
SELECT enum_last(NULL::planets);
ALTER TYPE planets ADD VALUE IF NOT EXISTS 'pluto';
-- should be pluto, i.e. the new value
SELECT enum_last(NULL::planets);
-- --
-- Test inserting so many values that we have to renumber -- Test inserting so many values that we have to renumber
-- --