From 6d12b68cd7a93e279c8c690749b334c9f59ac7fa Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Sat, 22 Sep 2012 12:53:31 -0400 Subject: [PATCH] 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. --- doc/src/sgml/ref/alter_type.sgml | 9 +++++++-- src/backend/catalog/pg_enum.c | 18 +++++++++++++++++- src/backend/commands/typecmds.c | 3 ++- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/parser/gram.y | 25 ++++++++++++++++--------- src/include/catalog/pg_enum.h | 3 ++- src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/enum.out | 22 ++++++++++++++++++++++ src/test/regress/sql/enum.sql | 20 ++++++++++++++++++++ 10 files changed, 89 insertions(+), 14 deletions(-) diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index 6386085a60..1ff53923fb 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -28,7 +28,7 @@ ALTER TYPE name OWNER TO name RENAME ATTRIBUTE attribute_name TO new_attribute_name ALTER TYPE name RENAME TO new_name [ CASCADE | RESTRICT ] ALTER TYPE name SET SCHEMA new_schema -ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ] +ALTER TYPE name ADD VALUE [ IF NOT EXISTS ] new_enum_value [ { BEFORE | AFTER } existing_enum_value ] where action is one of: @@ -106,7 +106,7 @@ ALTER TYPE name ADD VALUE - ADD VALUE [ BEFORE | AFTER ] + ADD VALUE [ IF NOT EXISTS ] [ BEFORE | AFTER ] This form adds a new value to an enum type. If the new value's place in @@ -114,6 +114,11 @@ ALTER TYPE name ADD VALUE AFTER, then the new item is placed at the end of the list of values. + + If IF NOT EXISTS diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c index 8ddb376bc1..f3161efb20 100644 --- a/src/backend/catalog/pg_enum.c +++ b/src/backend/catalog/pg_enum.c @@ -179,7 +179,8 @@ void AddEnumLabel(Oid enumTypeOid, const char *newVal, const char *neighbor, - bool newValIsAfter) + bool newValIsAfter, + bool skipIfExists) { Relation pg_enum; Oid newOid; @@ -211,6 +212,21 @@ AddEnumLabel(Oid enumTypeOid, */ 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); /* If we have to renumber the existing members, we restart from here */ diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 4c9d00386c..6cb6fd56fd 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1188,7 +1188,8 @@ AlterEnum(AlterEnumStmt *stmt) /* Add the new label */ AddEnumLabel(enum_type_oid, stmt->newVal, - stmt->newValNeighbor, stmt->newValIsAfter); + stmt->newValNeighbor, stmt->newValIsAfter, + stmt->skipIfExists); ReleaseSysCache(tup); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f34f7049e4..34d4f40fe2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3055,6 +3055,7 @@ _copyAlterEnumStmt(const AlterEnumStmt *from) COPY_STRING_FIELD(newVal); COPY_STRING_FIELD(newValNeighbor); COPY_SCALAR_FIELD(newValIsAfter); + COPY_SCALAR_FIELD(skipIfExists); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b4b1c22336..f63f4973db 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1439,6 +1439,7 @@ _equalAlterEnumStmt(const AlterEnumStmt *a, const AlterEnumStmt *b) COMPARE_STRING_FIELD(newVal); COMPARE_STRING_FIELD(newValNeighbor); COMPARE_SCALAR_FIELD(newValIsAfter); + COMPARE_SCALAR_FIELD(skipIfExists); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 5894cb0885..ec88b71076 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -470,7 +470,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type window_definition over_clause window_specification opt_frame_clause frame_extent frame_bound %type opt_existing_window_name - +%type opt_if_not_exists /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -4618,35 +4618,42 @@ enum_val_list: Sconst *****************************************************************************/ 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); n->typeName = $3; - n->newVal = $6; + n->newVal = $7; n->newValNeighbor = NULL; n->newValIsAfter = true; + n->skipIfExists = $6; $$ = (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); n->typeName = $3; - n->newVal = $6; - n->newValNeighbor = $8; + n->newVal = $7; + n->newValNeighbor = $9; n->newValIsAfter = false; + n->skipIfExists = $6; $$ = (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); n->typeName = $3; - n->newVal = $6; - n->newValNeighbor = $8; + n->newVal = $7; + n->newValNeighbor = $9; n->newValIsAfter = true; + n->skipIfExists = $6; $$ = (Node *) n; } ; +opt_if_not_exists: IF_P NOT EXISTS { $$ = true; } + | /* empty */ { $$ = false; } + ; + /***************************************************************************** * diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h index 91c1ab1de7..8842a8706b 100644 --- a/src/include/catalog/pg_enum.h +++ b/src/include/catalog/pg_enum.h @@ -65,6 +65,7 @@ typedef FormData_pg_enum *Form_pg_enum; extern void EnumValuesCreate(Oid enumTypeOid, List *vals); extern void EnumValuesDelete(Oid enumTypeOid); extern void AddEnumLabel(Oid enumTypeOid, const char *newVal, - const char *neighbor, bool newValIsAfter); + const char *neighbor, bool newValIsAfter, + bool skipIfExists); #endif /* PG_ENUM_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 19178b5551..98fe850c92 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2306,6 +2306,7 @@ typedef struct AlterEnumStmt char *newVal; /* new enum value's name */ char *newValNeighbor; /* neighboring enum value, if specified */ bool newValIsAfter; /* place new enum value after neighbor? */ + bool skipIfExists; /* ignore statement if label already exists */ } AlterEnumStmt; /* ---------------------- diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out index bf94af5ef6..a14097297a 100644 --- a/src/test/regress/expected/enum.out +++ b/src/test/regress/expected/enum.out @@ -95,6 +95,28 @@ ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutopl DETAIL: Labels must be 63 characters or less. ALTER TYPE planets ADD VALUE 'pluto' AFTER 'zeus'; 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 -- diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql index 7ca6248309..db7bf44b40 100644 --- a/src/test/regress/sql/enum.sql +++ b/src/test/regress/sql/enum.sql @@ -54,6 +54,26 @@ ALTER TYPE planets ADD VALUE 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 --