diff --git a/contrib/pg_upgrade/function.c b/contrib/pg_upgrade/function.c index 31255d637d..c76aaeb090 100644 --- a/contrib/pg_upgrade/function.c +++ b/contrib/pg_upgrade/function.c @@ -79,7 +79,7 @@ install_support_functions(void) "LANGUAGE C STRICT;")); PQclear(executeQueryOrDie(conn, "CREATE OR REPLACE FUNCTION " - " binary_upgrade.add_pg_enum_label(OID, OID, NAME) " + " binary_upgrade.set_next_pg_enum_oid(OID) " "RETURNS VOID " "AS '$libdir/pg_upgrade_support' " "LANGUAGE C STRICT;")); diff --git a/contrib/pg_upgrade_support/pg_upgrade_support.c b/contrib/pg_upgrade_support/pg_upgrade_support.c index c956be187a..3ec436fe14 100644 --- a/contrib/pg_upgrade_support/pg_upgrade_support.c +++ b/contrib/pg_upgrade_support/pg_upgrade_support.c @@ -16,13 +16,6 @@ /* THIS IS USED ONLY FOR PG >= 9.0 */ -/* - * Cannot include "catalog/pg_enum.h" here because we might - * not be compiling against PG 9.0. - */ -extern void EnumValuesCreate(Oid enumTypeOid, List *vals, - Oid binary_upgrade_next_pg_enum_oid); - #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif @@ -33,6 +26,7 @@ extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid; extern PGDLLIMPORT Oid binary_upgrade_next_heap_relfilenode; extern PGDLLIMPORT Oid binary_upgrade_next_toast_relfilenode; extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode; +extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_oid; Datum set_next_pg_type_oid(PG_FUNCTION_ARGS); Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS); @@ -40,7 +34,7 @@ Datum set_next_pg_type_toast_oid(PG_FUNCTION_ARGS); Datum set_next_heap_relfilenode(PG_FUNCTION_ARGS); Datum set_next_toast_relfilenode(PG_FUNCTION_ARGS); Datum set_next_index_relfilenode(PG_FUNCTION_ARGS); -Datum add_pg_enum_label(PG_FUNCTION_ARGS); +Datum set_next_pg_enum_oid(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(set_next_pg_type_oid); PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid); @@ -48,7 +42,7 @@ PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid); PG_FUNCTION_INFO_V1(set_next_heap_relfilenode); PG_FUNCTION_INFO_V1(set_next_toast_relfilenode); PG_FUNCTION_INFO_V1(set_next_index_relfilenode); -PG_FUNCTION_INFO_V1(add_pg_enum_label); +PG_FUNCTION_INFO_V1(set_next_pg_enum_oid); Datum set_next_pg_type_oid(PG_FUNCTION_ARGS) @@ -111,14 +105,11 @@ set_next_index_relfilenode(PG_FUNCTION_ARGS) } Datum -add_pg_enum_label(PG_FUNCTION_ARGS) +set_next_pg_enum_oid(PG_FUNCTION_ARGS) { Oid enumoid = PG_GETARG_OID(0); - Oid typoid = PG_GETARG_OID(1); - Name label = PG_GETARG_NAME(2); - EnumValuesCreate(typoid, list_make1(makeString(NameStr(*label))), - enumoid); + binary_upgrade_next_pg_enum_oid = enumoid; PG_RETURN_VOID(); } diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b7b48e4fb9..9a8729b8b3 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2623,12 +2623,9 @@ The pg_enum catalog contains entries - matching enum types to their associated values and labels. The + showing the values and labels for each enum type. The internal representation of a given enum value is actually the OID - of its associated row in pg_enum. The - OIDs for a particular enum type are guaranteed to be ordered in - the way the type should sort, but there is no guarantee about the - ordering of OIDs of unrelated enum types. + of its associated row in pg_enum. @@ -2652,6 +2649,13 @@ The OID of the pg_type entry owning this enum value + + enumsortorder + float4 + + The sort position of this enum value within its enum type + + enumlabel name @@ -2661,6 +2665,26 @@
+ + + The OIDs for pg_enum rows follow a special + rule: even-numbered OIDs are guaranteed to be ordered in the same way + as the sort ordering of their enum type. That is, if two even OIDs + belong to the same enum type, the smaller OID must have the smaller + enumsortorder value. Odd-numbered OID values + need bear no relationship to the sort order. This rule allows the + enum comparison routines to avoid catalog lookups in many common cases. + The routines that create and alter enum types attempt to assign even + OIDs to enum values whenever possible. + + + + When an enum type is created, its members are assigned sort-order + positions 1..n. But members added later might be given + negative or fractional values of enumsortorder. + The only requirement on these values is that they be correctly + ordered and unique within each enum type. + diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml index 315922ea83..90de2e81ef 100644 --- a/doc/src/sgml/ref/alter_type.sgml +++ b/doc/src/sgml/ref/alter_type.sgml @@ -24,10 +24,11 @@ PostgreSQL documentation ALTER TYPE name action [, ... ] -ALTER TYPE name OWNER TO new_owner +ALTER TYPE name OWNER TO new_owner ALTER TYPE name RENAME ATTRIBUTE attribute_name TO new_attribute_name ALTER TYPE name RENAME TO new_name ALTER TYPE name SET SCHEMA new_schema +ALTER TYPE name ADD new_enum_value [ { BEFORE | AFTER } existing_enum_value ] where action is one of: @@ -103,6 +104,18 @@ ALTER TYPE name SET SCHEMA + + + ADD [ BEFORE | AFTER ] + + + This form adds a new value to an enum type. If the new value's place in + the enum's ordering is not specified using BEFORE or + AFTER, then the new item is placed at the end of the + list of values. + + + @@ -181,7 +194,7 @@ ALTER TYPE name SET SCHEMA new_attribute_name - The new name of the attribute begin renamed. + The new name of the attribute to be renamed. @@ -196,10 +209,53 @@ ALTER TYPE name SET SCHEMA + + new_enum_value + + + The new value to be added to an enum type's list of values. + Like all enum literals, it needs to be quoted. + + + + + + existing_enum_value + + + The existing enum value that the new value should be added immediately + before or after in the enum type's sort ordering. + Like all enum literals, it needs to be quoted. + + + + + + Notes + + + ALTER TYPE ... ADD (the form that adds a new value to an + enum type) cannot be executed inside a transaction block. + + + + Comparisons involving an added enum value will sometimes be slower than + comparisons involving only original members of the enum type. This will + usually only occur if BEFORE or AFTER + is used to set the new value's sort position somewhere other than at the + end of the list. However, sometimes it will happen even though the new + value is added at the end (this occurs if the OID counter wrapped + around since the original creation of the enum type). The slowdown is + usually insignificant; but if it matters, optimal performance can be + regained by dropping and recreating the enum type, or by dumping and + reloading the database. + + + Examples @@ -230,6 +286,13 @@ ALTER TYPE email SET SCHEMA customers; To add a new attribute to a type: ALTER TYPE compfoo ADD ATTRIBUTE f3 int; + + + + + To add a new value to an enum type in a particular sort position: + +ALTER TYPE colors ADD 'orange' AFTER 'red'; diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c index d544c1f477..0c384def7b 100644 --- a/src/backend/catalog/pg_enum.c +++ b/src/backend/catalog/pg_enum.c @@ -15,15 +15,24 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/xact.h" #include "catalog/catalog.h" #include "catalog/indexing.h" #include "catalog/pg_enum.h" +#include "catalog/pg_type.h" +#include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/tqual.h" + +Oid binary_upgrade_next_pg_enum_oid = InvalidOid; + +static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems); static int oid_cmp(const void *p1, const void *p2); +static int sort_order_cmp(const void *p1, const void *p2); /* @@ -33,11 +42,9 @@ static int oid_cmp(const void *p1, const void *p2); * vals is a list of Value strings. */ void -EnumValuesCreate(Oid enumTypeOid, List *vals, - Oid binary_upgrade_next_pg_enum_oid) +EnumValuesCreate(Oid enumTypeOid, List *vals) { Relation pg_enum; - TupleDesc tupDesc; NameData enumlabel; Oid *oids; int elemno, @@ -50,48 +57,42 @@ EnumValuesCreate(Oid enumTypeOid, List *vals, num_elems = list_length(vals); /* - * XXX we do not bother to check the list of values for duplicates --- if + * We do not bother to check the list of values for duplicates --- if * you have any, you'll get a less-than-friendly unique-index violation. - * Is it worth trying harder? + * It is probably not worth trying harder. */ pg_enum = heap_open(EnumRelationId, RowExclusiveLock); - tupDesc = pg_enum->rd_att; /* - * Allocate oids + * Allocate OIDs for the enum's members. + * + * While this method does not absolutely guarantee that we generate no + * duplicate OIDs (since we haven't entered each oid into the table + * before allocating the next), trouble could only occur if the OID + * counter wraps all the way around before we finish. Which seems + * unlikely. */ oids = (Oid *) palloc(num_elems * sizeof(Oid)); - if (OidIsValid(binary_upgrade_next_pg_enum_oid)) - { - if (num_elems != 1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("EnumValuesCreate() can only set a single OID"))); - oids[0] = binary_upgrade_next_pg_enum_oid; - binary_upgrade_next_pg_enum_oid = InvalidOid; - } - else + + for (elemno = 0; elemno < num_elems; elemno++) { /* - * While this method does not absolutely guarantee that we generate no - * duplicate oids (since we haven't entered each oid into the table - * before allocating the next), trouble could only occur if the oid - * counter wraps all the way around before we finish. Which seems - * unlikely. + * We assign even-numbered OIDs to all the new enum labels. This + * tells the comparison functions the OIDs are in the correct sort + * order and can be compared directly. */ - for (elemno = 0; elemno < num_elems; elemno++) - { - /* - * The pg_enum.oid is stored in user tables. This oid must be - * preserved by binary upgrades. - */ - oids[elemno] = GetNewOid(pg_enum); - } - /* sort them, just in case counter wrapped from high to low */ - qsort(oids, num_elems, sizeof(Oid), oid_cmp); + Oid new_oid; + + do { + new_oid = GetNewOid(pg_enum); + } while (new_oid & 1); + oids[elemno] = new_oid; } + /* sort them, just in case OID counter wrapped from high to low */ + qsort(oids, num_elems, sizeof(Oid), oid_cmp); + /* and make the entries */ memset(nulls, false, sizeof(nulls)); @@ -112,10 +113,11 @@ EnumValuesCreate(Oid enumTypeOid, List *vals, NAMEDATALEN - 1))); values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); + values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1); namestrcpy(&enumlabel, lab); values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); - tup = heap_form_tuple(tupDesc, values, nulls); + tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls); HeapTupleSetOid(tup, oids[elemno]); simple_heap_insert(pg_enum, tup); @@ -164,7 +166,322 @@ EnumValuesDelete(Oid enumTypeOid) } -/* qsort comparison function */ +/* + * AddEnumLabel + * Add a new label to the enum set. By default it goes at + * the end, but the user can choose to place it before or + * after any existing set member. + */ +void +AddEnumLabel(Oid enumTypeOid, + const char *newVal, + const char *neighbor, + bool newValIsAfter) +{ + Relation pg_enum; + Oid newOid; + Datum values[Natts_pg_enum]; + bool nulls[Natts_pg_enum]; + NameData enumlabel; + HeapTuple enum_tup; + float4 newelemorder; + HeapTuple *existing; + CatCList *list; + int nelems; + 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. Without that, we couldn't be sure to get a consistent + * view of the enum members via the syscache. Note that this does not + * block other backends from inspecting the type; see comments for + * RenumberEnumType. + */ + LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock); + + pg_enum = heap_open(EnumRelationId, RowExclusiveLock); + + /* If we have to renumber the existing members, we restart from here */ +restart: + + /* Get the list of existing members of the enum */ + list = SearchSysCacheList1(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumTypeOid)); + nelems = list->n_members; + + /* Sort the existing members by enumsortorder */ + existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple)); + for (i = 0; i < nelems; i++) + existing[i] = &(list->members[i]->tuple); + + qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp); + + if (neighbor == NULL) + { + /* + * Put the new label at the end of the list. + * No change to existing tuples is required. + */ + if (nelems > 0) + { + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]); + + newelemorder = en->enumsortorder + 1; + } + else + newelemorder = 1; + } + else + { + /* BEFORE or AFTER was specified */ + int nbr_index; + int other_nbr_index; + Form_pg_enum nbr_en; + Form_pg_enum other_nbr_en; + + /* Locate the neighbor element */ + for (nbr_index = 0; nbr_index < nelems; nbr_index++) + { + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]); + + if (strcmp(NameStr(en->enumlabel), neighbor) == 0) + break; + } + if (nbr_index >= nelems) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" is not an existing enum label", + neighbor))); + nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]); + + /* + * Attempt to assign an appropriate enumsortorder value: one less + * than the smallest member, one more than the largest member, + * or halfway between two existing members. + * + * In the "halfway" case, because of the finite precision of float4, + * we might compute a value that's actually equal to one or the + * other of its neighbors. In that case we renumber the existing + * members and try again. + */ + if (newValIsAfter) + other_nbr_index = nbr_index + 1; + else + other_nbr_index = nbr_index - 1; + + if (other_nbr_index < 0) + newelemorder = nbr_en->enumsortorder - 1; + else if (other_nbr_index >= nelems) + newelemorder = nbr_en->enumsortorder + 1; + else + { + other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]); + newelemorder = (nbr_en->enumsortorder + + other_nbr_en->enumsortorder) / 2; + if (newelemorder == nbr_en->enumsortorder || + newelemorder == other_nbr_en->enumsortorder) + { + RenumberEnumType(pg_enum, existing, nelems); + /* Clean up and start over */ + pfree(existing); + ReleaseCatCacheList(list); + goto restart; + } + } + } + + /* Get a new OID for the new label */ + if (OidIsValid(binary_upgrade_next_pg_enum_oid)) + { + /* + * In binary upgrades, just add the new label with the predetermined + * Oid. It's pg_upgrade's responsibility that the Oid meets + * requirements. + */ + if (neighbor != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade"))); + + newOid = binary_upgrade_next_pg_enum_oid; + binary_upgrade_next_pg_enum_oid = InvalidOid; + } + else + { + /* + * Normal case: we need to allocate a new Oid for the value. + * + * We want to give the new element an even-numbered Oid if it's safe, + * which is to say it compares correctly to all pre-existing even + * numbered Oids in the enum. Otherwise, we must give it an odd Oid. + */ + for (;;) + { + bool sorts_ok; + + /* Get a new OID (different from all existing pg_enum tuples) */ + newOid = GetNewOid(pg_enum); + + /* + * Detect whether it sorts correctly relative to existing + * even-numbered labels of the enum. We can ignore existing + * labels with odd Oids, since a comparison involving one of + * those will not take the fast path anyway. + */ + sorts_ok = true; + for (i = 0; i < nelems; i++) + { + HeapTuple exists_tup = existing[i]; + Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup); + Oid exists_oid = HeapTupleGetOid(exists_tup); + + if (exists_oid & 1) + continue; /* ignore odd Oids */ + + if (exists_en->enumsortorder < newelemorder) + { + /* should sort before */ + if (exists_oid >= newOid) + { + sorts_ok = false; + break; + } + } + else + { + /* should sort after */ + if (exists_oid <= newOid) + { + sorts_ok = false; + break; + } + } + } + + if (sorts_ok) + { + /* If it's even and sorts OK, we're done. */ + if ((newOid & 1) == 0) + break; + + /* + * If it's odd, and sorts OK, loop back to get another OID + * and try again. Probably, the next available even OID + * will sort correctly too, so it's worth trying. + */ + } + else + { + /* + * If it's odd, and does not sort correctly, we're done. + * (Probably, the next available even OID would sort + * incorrectly too, so no point in trying again.) + */ + if (newOid & 1) + break; + + /* + * If it's even, and does not sort correctly, loop back to get + * another OID and try again. (We *must* reject this case.) + */ + } + } + } + + /* Done with info about existing members */ + pfree(existing); + ReleaseCatCacheList(list); + + /* Create the new pg_enum entry */ + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); + values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder); + namestrcpy(&enumlabel, newVal); + values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); + enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls); + HeapTupleSetOid(enum_tup, newOid); + simple_heap_insert(pg_enum, 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. + * + * We avoid doing this unless absolutely necessary; in most installations + * it will never happen. The reason is that updating existing pg_enum + * entries creates hazards for other backends that are concurrently reading + * pg_enum with SnapshotNow semantics. A concurrent SnapshotNow scan could + * see both old and new versions of an updated row as valid, or neither of + * them, if the commit happens between scanning the two versions. It's + * also quite likely for a concurrent scan to see an inconsistent set of + * rows (some members updated, some not). + * + * We can avoid these risks by reading pg_enum with an MVCC snapshot + * instead of SnapshotNow, but that forecloses use of the syscaches. + * We therefore make the following choices: + * + * 1. Any code that is interested in the enumsortorder values MUST read + * pg_enum with an MVCC snapshot, or else acquire lock on the enum type + * to prevent concurrent execution of AddEnumLabel(). The risk of + * seeing inconsistent values of enumsortorder is too high otherwise. + * + * 2. Code that is not examining enumsortorder can use a syscache + * (for example, enum_in and enum_out do so). The worst that can happen + * is a transient failure to find any valid value of the row. This is + * judged acceptable in view of the infrequency of use of RenumberEnumType. + */ +static void +RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems) +{ + int i; + + /* + * We should only need to increase existing elements' enumsortorders, + * never decrease them. Therefore, work from the end backwards, to avoid + * unwanted uniqueness violations. + */ + for (i = nelems - 1; i >= 0; i--) + { + HeapTuple newtup; + Form_pg_enum en; + float4 newsortorder; + + newtup = heap_copytuple(existing[i]); + en = (Form_pg_enum) GETSTRUCT(newtup); + + newsortorder = i + 1; + if (en->enumsortorder != newsortorder) + { + en->enumsortorder = newsortorder; + + simple_heap_update(pg_enum, &newtup->t_self, newtup); + + CatalogUpdateIndexes(pg_enum, newtup); + } + + heap_freetuple(newtup); + } + + /* Make the updates visible */ + CommandCounterIncrement(); +} + + +/* qsort comparison function for oids */ static int oid_cmp(const void *p1, const void *p2) { @@ -177,3 +494,20 @@ oid_cmp(const void *p1, const void *p2) return 1; return 0; } + +/* qsort comparison function for tuples by sort order */ +static int +sort_order_cmp(const void *p1, const void *p2) +{ + HeapTuple v1 = *((const HeapTuple *) p1); + HeapTuple v2 = *((const HeapTuple *) p2); + Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1); + Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2); + + if (en1->enumsortorder < en2->enumsortorder) + return -1; + else if (en1->enumsortorder > en2->enumsortorder) + return 1; + else + return 0; +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 46b156e09a..220be9b443 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -84,7 +84,8 @@ static Oid findTypeTypmodinFunction(List *procname); static Oid findTypeTypmodoutFunction(List *procname); static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); -static void checkDomainOwner(HeapTuple tup, TypeName *typename); +static void checkDomainOwner(HeapTuple tup); +static void checkEnumOwner(HeapTuple tup); static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, int typMod, Constraint *constr, @@ -1150,7 +1151,7 @@ DefineEnum(CreateEnumStmt *stmt) false); /* Type NOT NULL */ /* Enter the enum's values into pg_enum */ - EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid); + EnumValuesCreate(enumTypeOid, stmt->vals); /* * Create the array type that goes with it. @@ -1191,6 +1192,60 @@ DefineEnum(CreateEnumStmt *stmt) pfree(enumArrayName); } +/* + * AlterEnum + * Adds a new label to an existing enum. + */ +void +AlterEnum(AlterEnumStmt *stmt) +{ + Oid enum_type_oid; + TypeName *typename; + HeapTuple tup; + + /* Make a TypeName so we can use standard type lookup machinery */ + typename = makeTypeNameFromNameList(stmt->typeName); + enum_type_oid = typenameTypeId(NULL, typename, NULL); + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", enum_type_oid); + + /* 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); + + ReleaseSysCache(tup); +} + + +/* + * checkEnumOwner + * + * Check that the type is actually an enum and that the current user + * has permission to do ALTER TYPE on it. Throw an error if not. + */ +static void +checkEnumOwner(HeapTuple tup) +{ + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + + /* Check that this is actually an enum */ + if (typTup->typtype != TYPTYPE_ENUM) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not an enum", + format_type_be(HeapTupleGetOid(tup))))); + + /* Permission check: must own type */ + if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, + format_type_be(HeapTupleGetOid(tup))); +} + /* * Find suitable I/O functions for a type. @@ -1576,7 +1631,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) typTup = (Form_pg_type) GETSTRUCT(tup); /* Check it's a domain and check user has permission for ALTER DOMAIN */ - checkDomainOwner(tup, typename); + checkDomainOwner(tup); /* Setup new tuple */ MemSet(new_record, (Datum) 0, sizeof(new_record)); @@ -1702,7 +1757,7 @@ AlterDomainNotNull(List *names, bool notNull) typTup = (Form_pg_type) GETSTRUCT(tup); /* Check it's a domain and check user has permission for ALTER DOMAIN */ - checkDomainOwner(tup, typename); + checkDomainOwner(tup); /* Is the domain already set to the desired constraint? */ if (typTup->typnotnull == notNull) @@ -1801,7 +1856,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, elog(ERROR, "cache lookup failed for type %u", domainoid); /* Check it's a domain and check user has permission for ALTER DOMAIN */ - checkDomainOwner(tup, typename); + checkDomainOwner(tup); /* Grab an appropriate lock on the pg_constraint relation */ conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -1875,7 +1930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint) typTup = (Form_pg_type) GETSTRUCT(tup); /* Check it's a domain and check user has permission for ALTER DOMAIN */ - checkDomainOwner(tup, typename); + checkDomainOwner(tup); if (!IsA(newConstraint, Constraint)) elog(ERROR, "unrecognized node type: %d", @@ -2187,7 +2242,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode) * has permission to do ALTER DOMAIN on it. Throw an error if not. */ static void -checkDomainOwner(HeapTuple tup, TypeName *typename) +checkDomainOwner(HeapTuple tup) { Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); @@ -2195,8 +2250,8 @@ checkDomainOwner(HeapTuple tup, TypeName *typename) if (typTup->typtype != TYPTYPE_DOMAIN) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a domain", - TypeNameToString(typename)))); + errmsg("%s is not a domain", + format_type_be(HeapTupleGetOid(tup))))); /* Permission check: must own type */ if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 508d7c70b1..5346c72cd9 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2901,6 +2901,19 @@ _copyCreateEnumStmt(CreateEnumStmt *from) return newnode; } +static AlterEnumStmt * +_copyAlterEnumStmt(AlterEnumStmt *from) +{ + AlterEnumStmt *newnode = makeNode(AlterEnumStmt); + + COPY_NODE_FIELD(typeName); + COPY_STRING_FIELD(newVal); + COPY_STRING_FIELD(newValNeighbor); + COPY_SCALAR_FIELD(newValIsAfter); + + return newnode; +} + static ViewStmt * _copyViewStmt(ViewStmt *from) { @@ -4064,6 +4077,9 @@ copyObject(void *from) case T_CreateEnumStmt: retval = _copyCreateEnumStmt(from); break; + case T_AlterEnumStmt: + retval = _copyAlterEnumStmt(from); + break; case T_ViewStmt: retval = _copyViewStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 19262aad66..7cb2192d94 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1392,6 +1392,17 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b) return true; } +static bool +_equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_STRING_FIELD(newVal); + COMPARE_STRING_FIELD(newValNeighbor); + COMPARE_SCALAR_FIELD(newValIsAfter); + + return true; +} + static bool _equalViewStmt(ViewStmt *a, ViewStmt *b) { @@ -2700,6 +2711,9 @@ equal(void *a, void *b) case T_CreateEnumStmt: retval = _equalCreateEnumStmt(a, b); break; + case T_AlterEnumStmt: + retval = _equalAlterEnumStmt(a, b); + break; case T_ViewStmt: retval = _equalViewStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c4165f0bf0..1394b21dec 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -182,8 +182,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ } %type stmt schema_stmt - AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt - AlterForeignServerStmt AlterGroupStmt + AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt + AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt @@ -652,6 +652,7 @@ stmt : | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt + | AlterEnumStmt | AlterFdwStmt | AlterForeignServerStmt | AlterFunctionStmt @@ -3863,6 +3864,42 @@ enum_val_list: Sconst { $$ = lappend($1, makeString($3)); } ; +/***************************************************************************** + * + * ALTER TYPE enumtype ADD ... + * + *****************************************************************************/ + +AlterEnumStmt: + ALTER TYPE_P any_name ADD_P Sconst + { + AlterEnumStmt *n = makeNode(AlterEnumStmt); + n->typeName = $3; + n->newVal = $5; + n->newValNeighbor = NULL; + n->newValIsAfter = true; + $$ = (Node *) n; + } + | ALTER TYPE_P any_name ADD_P Sconst BEFORE Sconst + { + AlterEnumStmt *n = makeNode(AlterEnumStmt); + n->typeName = $3; + n->newVal = $5; + n->newValNeighbor = $7; + n->newValIsAfter = false; + $$ = (Node *) n; + } + | ALTER TYPE_P any_name ADD_P Sconst AFTER Sconst + { + AlterEnumStmt *n = makeNode(AlterEnumStmt); + n->typeName = $3; + n->newVal = $5; + n->newValNeighbor = $7; + n->newValIsAfter = true; + $$ = (Node *) n; + } + ; + /***************************************************************************** * diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 75cb354ea8..2300e88249 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -190,6 +190,7 @@ check_xact_readonly(Node *parsetree) case T_CreateTrigStmt: case T_CompositeTypeStmt: case T_CreateEnumStmt: + case T_AlterEnumStmt: case T_ViewStmt: case T_DropCastStmt: case T_DropStmt: @@ -860,6 +861,16 @@ standard_ProcessUtility(Node *parsetree, DefineEnum((CreateEnumStmt *) parsetree); break; + case T_AlterEnumStmt: /* ALTER TYPE (enum) */ + /* + * We disallow this in transaction blocks, because we can't cope + * with enum OID values getting into indexes and then having their + * defining pg_enum entries go away. + */ + PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD"); + AlterEnum((AlterEnumStmt *) parsetree); + break; + case T_ViewStmt: /* CREATE VIEW */ DefineView((ViewStmt *) parsetree, queryString); break; @@ -1868,6 +1879,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE TYPE"; break; + case T_AlterEnumStmt: + tag = "ALTER TYPE"; + break; + case T_ViewStmt: tag = "CREATE VIEW"; break; @@ -2410,6 +2425,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_AlterEnumStmt: + lev = LOGSTMT_DDL; + break; + case T_ViewStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c index e5747a46bc..dd168dd973 100644 --- a/src/backend/utils/adt/enum.c +++ b/src/backend/utils/adt/enum.c @@ -13,18 +13,22 @@ */ #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/indexing.h" #include "catalog/pg_enum.h" #include "fmgr.h" +#include "libpq/pqformat.h" #include "utils/array.h" #include "utils/builtins.h" -#include "utils/lsyscache.h" +#include "utils/fmgroids.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" -#include "libpq/pqformat.h" -#include "miscadmin.h" +#include "utils/typcache.h" +static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction); static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper); -static int enum_elem_cmp(const void *left, const void *right); /* Basic I/O support */ @@ -155,13 +159,63 @@ enum_send(PG_FUNCTION_ARGS) /* Comparison functions and related */ +/* + * enum_cmp_internal is the common engine for all the visible comparison + * functions, except for enum_eq and enum_ne which can just check for OID + * equality directly. + */ +static int +enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo) +{ + TypeCacheEntry *tcache; + + /* Equal OIDs are equal no matter what */ + if (arg1 == arg2) + return 0; + + /* Fast path: even-numbered Oids are known to compare correctly */ + if ((arg1 & 1) == 0 && (arg2 & 1) == 0) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* Locate the typcache entry for the enum type */ + tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (tcache == NULL) + { + HeapTuple enum_tup; + Form_pg_enum en; + Oid typeoid; + + /* Get the OID of the enum type containing arg1 */ + enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1)); + if (!HeapTupleIsValid(enum_tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + arg1))); + en = (Form_pg_enum) GETSTRUCT(enum_tup); + typeoid = en->enumtypid; + ReleaseSysCache(enum_tup); + /* Now locate and remember the typcache entry */ + tcache = lookup_type_cache(typeoid, 0); + fcinfo->flinfo->fn_extra = (void *) tcache; + } + + /* The remaining comparison logic is in typcache.c */ + return compare_values_of_enum(tcache, arg1, arg2); +} + Datum enum_lt(PG_FUNCTION_ARGS) { Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a < b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0); } Datum @@ -170,7 +224,7 @@ enum_le(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a <= b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0); } Datum @@ -197,7 +251,7 @@ enum_ge(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a >= b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0); } Datum @@ -206,7 +260,7 @@ enum_gt(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a > b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0); } Datum @@ -215,7 +269,7 @@ enum_smaller(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_OID(a <= b ? a : b); + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b); } Datum @@ -224,7 +278,7 @@ enum_larger(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_OID(a >= b ? a : b); + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b); } Datum @@ -233,24 +287,63 @@ enum_cmp(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - if (a > b) - PG_RETURN_INT32(1); - else if (a == b) + if (a == b) PG_RETURN_INT32(0); + else if (enum_cmp_internal(a, b, fcinfo) > 0) + PG_RETURN_INT32(1); else PG_RETURN_INT32(-1); } /* Enum programming support functions */ +/* + * enum_endpoint: common code for enum_first/enum_last + */ +static Oid +enum_endpoint(Oid enumtypoid, ScanDirection direction) +{ + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + Oid minmax; + + /* + * Find the first/last enum member using pg_enum_typid_sortorder_index. + * Note we must not use the syscache, and must use an MVCC snapshot here. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); + + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, + GetTransactionSnapshot(), + 1, &skey); + + enum_tuple = systable_getnext_ordered(enum_scan, direction); + if (HeapTupleIsValid(enum_tuple)) + minmax = HeapTupleGetOid(enum_tuple); + else + minmax = InvalidOid; + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + heap_close(enum_rel, AccessShareLock); + + return minmax; +} + Datum enum_first(PG_FUNCTION_ARGS) { Oid enumtypoid; - Oid min = InvalidOid; - CatCList *list; - int num, - i; + Oid min; /* * We rely on being able to get the specific enum type from the calling @@ -263,21 +356,14 @@ enum_first(PG_FUNCTION_ARGS) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual enum type"))); - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - num = list->n_members; - for (i = 0; i < num; i++) - { - Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); + /* Get the OID using the index */ + min = enum_endpoint(enumtypoid, ForwardScanDirection); - if (!OidIsValid(min) || valoid < min) - min = valoid; - } - - ReleaseCatCacheList(list); - - if (!OidIsValid(min)) /* should not happen */ - elog(ERROR, "no values found for enum %s", - format_type_be(enumtypoid)); + if (!OidIsValid(min)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); PG_RETURN_OID(min); } @@ -286,10 +372,7 @@ Datum enum_last(PG_FUNCTION_ARGS) { Oid enumtypoid; - Oid max = InvalidOid; - CatCList *list; - int num, - i; + Oid max; /* * We rely on being able to get the specific enum type from the calling @@ -302,21 +385,14 @@ enum_last(PG_FUNCTION_ARGS) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual enum type"))); - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - num = list->n_members; - for (i = 0; i < num; i++) - { - Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); + /* Get the OID using the index */ + max = enum_endpoint(enumtypoid, BackwardScanDirection); - if (!OidIsValid(max) || valoid > max) - max = valoid; - } - - ReleaseCatCacheList(list); - - if (!OidIsValid(max)) /* should not happen */ - elog(ERROR, "no values found for enum %s", - format_type_be(enumtypoid)); + if (!OidIsValid(max)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); PG_RETURN_OID(max); } @@ -377,51 +453,68 @@ static ArrayType * enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) { ArrayType *result; - CatCList *list; - int total, - i, - j; + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; Datum *elems; + int max, + cnt; + bool left_found; - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - total = list->n_members; + /* + * Scan the enum members in order using pg_enum_typid_sortorder_index. + * Note we must not use the syscache, and must use an MVCC snapshot here. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); - elems = (Datum *) palloc(total * sizeof(Datum)); + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, + GetTransactionSnapshot(), + 1, &skey); - j = 0; - for (i = 0; i < total; i++) + max = 64; + elems = (Datum *) palloc(max * sizeof(Datum)); + cnt = 0; + left_found = !OidIsValid(lower); + + while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection))) { - Oid val = HeapTupleGetOid(&(list->members[i]->tuple)); + Oid enum_oid = HeapTupleGetOid(enum_tuple); - if ((!OidIsValid(lower) || lower <= val) && - (!OidIsValid(upper) || val <= upper)) - elems[j++] = ObjectIdGetDatum(val); + if (!left_found && lower == enum_oid) + left_found = true; + + if (left_found) + { + if (cnt >= max) + { + max *= 2; + elems = (Datum *) repalloc(elems, max * sizeof(Datum)); + } + + elems[cnt++] = ObjectIdGetDatum(enum_oid); + } + + if (OidIsValid(upper) && upper == enum_oid) + break; } - /* shouldn't need the cache anymore */ - ReleaseCatCacheList(list); - - /* sort results into OID order */ - qsort(elems, j, sizeof(Datum), enum_elem_cmp); + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + heap_close(enum_rel, AccessShareLock); + /* and build the result array */ /* note this hardwires some details about the representation of Oid */ - result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i'); + result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i'); pfree(elems); return result; } - -/* qsort comparison function for Datums that are OIDs */ -static int -enum_elem_cmp(const void *left, const void *right) -{ - Oid l = DatumGetObjectId(*((const Datum *) left)); - Oid r = DatumGetObjectId(*((const Datum *) right)); - - if (l < r) - return -1; - if (l > r) - return 1; - return 0; -} diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 2009614489..bc4abc451a 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -42,22 +42,44 @@ */ #include "postgres.h" +#include + #include "access/hash.h" #include "access/heapam.h" #include "access/nbtree.h" +#include "catalog/indexing.h" +#include "catalog/pg_enum.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/tqual.h" #include "utils/typcache.h" /* The main type cache hashtable searched by lookup_type_cache */ static HTAB *TypeCacheHash = NULL; +/* Private information to support comparisons of enum values */ +typedef struct +{ + Oid enum_oid; /* OID of one enum value */ + float4 sort_order; /* its sort position */ +} EnumItem; + +typedef struct TypeCacheEnumData +{ + Oid bitmap_base; /* OID corresponding to bit 0 of bitmapset */ + Bitmapset *sorted_values; /* Set of OIDs known to be in order */ + int num_values; /* total number of values in enum */ + EnumItem enum_values[1]; /* VARIABLE LENGTH ARRAY */ +} TypeCacheEnumData; + /* * We use a separate table for storing the definitions of non-anonymous * record types. Once defined, a record type will be remembered for the @@ -88,6 +110,9 @@ static int32 RecordCacheArrayLen = 0; /* allocated length of array */ static int32 NextRecordTypmod = 0; /* number of entries used */ static void TypeCacheRelCallback(Datum arg, Oid relid); +static void load_enum_cache_data(TypeCacheEntry *tcache); +static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg); +static int enum_oid_cmp(const void *left, const void *right); /* @@ -551,3 +576,300 @@ TypeCacheRelCallback(Datum arg, Oid relid) } } } + + +/* + * Check if given OID is part of the subset that's sortable by comparisons + */ +static inline bool +enum_known_sorted(TypeCacheEnumData *enumdata, Oid arg) +{ + Oid offset; + + if (arg < enumdata->bitmap_base) + return false; + offset = arg - enumdata->bitmap_base; + if (offset > (Oid) INT_MAX) + return false; + return bms_is_member((int) offset, enumdata->sorted_values); +} + + +/* + * compare_values_of_enum + * Compare two members of an enum type. + * Return <0, 0, or >0 according as arg1 <, =, or > arg2. + * + * Note: currently, the enumData cache is refreshed only if we are asked + * to compare an enum value that is not already in the cache. This is okay + * because there is no support for re-ordering existing values, so comparisons + * of previously cached values will return the right answer even if other + * values have been added since we last loaded the cache. + * + * Note: the enum logic has a special-case rule about even-numbered versus + * odd-numbered OIDs, but we take no account of that rule here; this + * routine shouldn't even get called when that rule applies. + */ +int +compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2) +{ + TypeCacheEnumData *enumdata; + EnumItem *item1; + EnumItem *item2; + + /* + * Equal OIDs are certainly equal --- this case was probably handled + * by our caller, but we may as well check. + */ + if (arg1 == arg2) + return 0; + + /* Load up the cache if first time through */ + if (tcache->enumData == NULL) + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + /* + * If both OIDs are known-sorted, we can just compare them directly. + */ + if (enum_known_sorted(enumdata, arg1) && + enum_known_sorted(enumdata, arg2)) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* + * Slow path: we have to identify their actual sort-order positions. + */ + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + if (item1 == NULL || item2 == NULL) + { + /* + * We couldn't find one or both values. That means the enum has + * changed under us, so re-initialize the cache and try again. + * We don't bother retrying the known-sorted case in this path. + */ + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + /* + * If we still can't find the values, complain: we must have + * corrupt data. + */ + if (item1 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg1, format_type_be(tcache->type_id)); + if (item2 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg2, format_type_be(tcache->type_id)); + } + + if (item1->sort_order < item2->sort_order) + return -1; + else if (item1->sort_order > item2->sort_order) + return 1; + else + return 0; +} + +/* + * Load (or re-load) the enumData member of the typcache entry. + */ +static void +load_enum_cache_data(TypeCacheEntry *tcache) +{ + TypeCacheEnumData *enumdata; + Relation enum_rel; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + EnumItem *items; + int numitems; + int maxitems; + Oid bitmap_base; + Bitmapset *bitmap; + MemoryContext oldcxt; + int bm_size, + start_pos; + + /* Check that this is actually an enum */ + if (tcache->typtype != TYPTYPE_ENUM) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not an enum", + format_type_be(tcache->type_id)))); + + /* + * Read all the information for members of the enum type. We collect + * the info in working memory in the caller's context, and then transfer + * it to permanent memory in CacheMemoryContext. This minimizes the risk + * of leaking memory from CacheMemoryContext in the event of an error + * partway through. + */ + maxitems = 64; + items = (EnumItem *) palloc(sizeof(EnumItem) * maxitems); + numitems = 0; + + /* + * Scan pg_enum for the members of the target enum type. We use a + * current MVCC snapshot, *not* SnapshotNow, so that we see a consistent + * set of rows even if someone commits a renumbering of the enum meanwhile. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tcache->type_id)); + + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_scan = systable_beginscan(enum_rel, + EnumTypIdLabelIndexId, + true, GetTransactionSnapshot(), + 1, &skey); + + while (HeapTupleIsValid(enum_tuple = systable_getnext(enum_scan))) + { + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enum_tuple); + + if (numitems >= maxitems) + { + maxitems *= 2; + items = (EnumItem *) repalloc(items, sizeof(EnumItem) * maxitems); + } + items[numitems].enum_oid = HeapTupleGetOid(enum_tuple); + items[numitems].sort_order = en->enumsortorder; + numitems++; + } + + systable_endscan(enum_scan); + heap_close(enum_rel, AccessShareLock); + + /* Sort the items into OID order */ + qsort(items, numitems, sizeof(EnumItem), enum_oid_cmp); + + /* + * Here, we create a bitmap listing a subset of the enum's OIDs that are + * known to be in order and can thus be compared with just OID comparison. + * + * The point of this is that the enum's initial OIDs were certainly in + * order, so there is some subset that can be compared via OID comparison; + * and we'd rather not do binary searches unnecessarily. + * + * This is somewhat heuristic, and might identify a subset of OIDs that + * isn't exactly what the type started with. That's okay as long as + * the subset is correctly sorted. + */ + bitmap_base = InvalidOid; + bitmap = NULL; + bm_size = 1; /* only save sets of at least 2 OIDs */ + + for (start_pos = 0; start_pos < numitems - 1; start_pos++) + { + /* + * Identify longest sorted subsequence starting at start_pos + */ + Bitmapset *this_bitmap = bms_make_singleton(0); + int this_bm_size = 1; + Oid start_oid = items[start_pos].enum_oid; + float4 prev_order = items[start_pos].sort_order; + int i; + + for (i = start_pos + 1; i < numitems; i++) + { + Oid offset; + + offset = items[i].enum_oid - start_oid; + /* quit if bitmap would be too large; cutoff is arbitrary */ + if (offset >= 8192) + break; + /* include the item if it's in-order */ + if (items[i].sort_order > prev_order) + { + prev_order = items[i].sort_order; + this_bitmap = bms_add_member(this_bitmap, (int) offset); + this_bm_size++; + } + } + + /* Remember it if larger than previous best */ + if (this_bm_size > bm_size) + { + bms_free(bitmap); + bitmap_base = start_oid; + bitmap = this_bitmap; + bm_size = this_bm_size; + } + else + bms_free(this_bitmap); + + /* + * Done if it's not possible to find a longer sequence in the rest + * of the list. In typical cases this will happen on the first + * iteration, which is why we create the bitmaps on the fly instead + * of doing a second pass over the list. + */ + if (bm_size >= (numitems - start_pos - 1)) + break; + } + + /* OK, copy the data into CacheMemoryContext */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + enumdata = (TypeCacheEnumData *) + palloc(offsetof(TypeCacheEnumData, enum_values) + + numitems * sizeof(EnumItem)); + enumdata->bitmap_base = bitmap_base; + enumdata->sorted_values = bms_copy(bitmap); + enumdata->num_values = numitems; + memcpy(enumdata->enum_values, items, numitems * sizeof(EnumItem)); + MemoryContextSwitchTo(oldcxt); + + pfree(items); + bms_free(bitmap); + + /* And link the finished cache struct into the typcache */ + if (tcache->enumData != NULL) + pfree(tcache->enumData); + tcache->enumData = enumdata; +} + +/* + * Locate the EnumItem with the given OID, if present + */ +static EnumItem * +find_enumitem(TypeCacheEnumData *enumdata, Oid arg) +{ + EnumItem srch; + + /* On some versions of Solaris, bsearch of zero items dumps core */ + if (enumdata->num_values <= 0) + return NULL; + + srch.enum_oid = arg; + return bsearch(&srch, enumdata->enum_values, enumdata->num_values, + sizeof(EnumItem), enum_oid_cmp); +} + +/* + * qsort comparison function for OID-ordered EnumItems + */ +static int +enum_oid_cmp(const void *left, const void *right) +{ + const EnumItem *l = (const EnumItem *) left; + const EnumItem *r = (const EnumItem *) right; + + if (l->enum_oid < r->enum_oid) + return -1; + else if (l->enum_oid > r->enum_oid) + return 1; + else + return 0; +} diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 6a4557b486..55ea6841a4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6657,14 +6657,21 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) Oid enum_oid; char *label; - /* Set proper schema search path so regproc references list correctly */ - selectSourceSchema(tyinfo->dobj.namespace->dobj.name); + /* Set proper schema search path */ + selectSourceSchema("pg_catalog"); - appendPQExpBuffer(query, "SELECT oid, enumlabel " - "FROM pg_catalog.pg_enum " - "WHERE enumtypid = '%u'" - "ORDER BY oid", - tyinfo->dobj.catId.oid); + if (fout->remoteVersion >= 90100) + appendPQExpBuffer(query, "SELECT oid, enumlabel " + "FROM pg_catalog.pg_enum " + "WHERE enumtypid = '%u'" + "ORDER BY enumsortorder", + tyinfo->dobj.catId.oid); + else + appendPQExpBuffer(query, "SELECT oid, enumlabel " + "FROM pg_catalog.pg_enum " + "WHERE enumtypid = '%u'" + "ORDER BY oid", + tyinfo->dobj.catId.oid); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); @@ -6713,13 +6720,15 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) if (i == 0) appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n"); appendPQExpBuffer(q, - "SELECT binary_upgrade.add_pg_enum_label('%u'::pg_catalog.oid, " - "'%u'::pg_catalog.oid, ", - enum_oid, tyinfo->dobj.catId.oid); + "SELECT binary_upgrade.set_next_pg_enum_oid('%u'::pg_catalog.oid);\n", + enum_oid); + appendPQExpBuffer(q, "ALTER TYPE %s.", + fmtId(tyinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(q, "%s ADD ", + fmtId(tyinfo->dobj.name)); appendStringLiteralAH(q, label, fout); - appendPQExpBuffer(q, ");\n"); + appendPQExpBuffer(q, ";\n\n"); } - appendPQExpBuffer(q, "\n"); } ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 57d74e14d7..b705cb29dd 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -473,17 +473,27 @@ describeTypes(const char *pattern, bool verbose, bool showSystem) gettext_noop("Internal name"), gettext_noop("Size")); if (verbose && pset.sversion >= 80300) + { appendPQExpBuffer(&buf, " pg_catalog.array_to_string(\n" " ARRAY(\n" " SELECT e.enumlabel\n" " FROM pg_catalog.pg_enum e\n" - " WHERE e.enumtypid = t.oid\n" - " ORDER BY e.oid\n" + " WHERE e.enumtypid = t.oid\n"); + + if (pset.sversion >= 90100) + appendPQExpBuffer(&buf, + " ORDER BY e.enumsortorder\n"); + else + appendPQExpBuffer(&buf, + " ORDER BY e.oid\n"); + + appendPQExpBuffer(&buf, " ),\n" " E'\\n'\n" " ) AS \"%s\",\n", gettext_noop("Elements")); + } appendPQExpBuffer(&buf, " pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n", diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index e30a7d7298..b858d3ee00 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201010201 +#define CATALOG_VERSION_NO 201010241 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index b7c9849314..a3839e1e25 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -147,6 +147,8 @@ DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops #define EnumOidIndexId 3502 DECLARE_UNIQUE_INDEX(pg_enum_typid_label_index, 3503, on pg_enum using btree(enumtypid oid_ops, enumlabel name_ops)); #define EnumTypIdLabelIndexId 3503 +DECLARE_UNIQUE_INDEX(pg_enum_typid_sortorder_index, 3534, on pg_enum using btree(enumtypid oid_ops, enumsortorder float4_ops)); +#define EnumTypIdSortOrderIndexId 3534 /* This following index is not used for a cache and is not unique */ DECLARE_INDEX(pg_index_indrelid_index, 2678, on pg_index using btree(indrelid oid_ops)); diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h index 28da42bf54..cc69eb531c 100644 --- a/src/include/catalog/pg_enum.h +++ b/src/include/catalog/pg_enum.h @@ -34,6 +34,7 @@ CATALOG(pg_enum,3501) { Oid enumtypid; /* OID of owning enum type */ + float4 enumsortorder; /* sort position of this enum value */ NameData enumlabel; /* text representation of enum value */ } FormData_pg_enum; @@ -48,9 +49,10 @@ typedef FormData_pg_enum *Form_pg_enum; * compiler constants for pg_enum * ---------------- */ -#define Natts_pg_enum 2 +#define Natts_pg_enum 3 #define Anum_pg_enum_enumtypid 1 -#define Anum_pg_enum_enumlabel 2 +#define Anum_pg_enum_enumsortorder 2 +#define Anum_pg_enum_enumlabel 3 /* ---------------- * pg_enum has no initial contents @@ -60,8 +62,9 @@ typedef FormData_pg_enum *Form_pg_enum; /* * prototypes for functions in pg_enum.c */ -extern void EnumValuesCreate(Oid enumTypeOid, List *vals, - Oid binary_upgrade_next_pg_enum_oid); +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); #endif /* PG_ENUM_H */ diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index 2bff7e1c2b..4c6e7e164e 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -24,6 +24,7 @@ extern void RemoveTypes(DropStmt *drop); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt); +extern void AlterEnum(AlterEnumStmt *stmt); extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); extern Oid AssignTypeArrayOid(void); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 15dabe31ce..8e94d9803f 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -338,6 +338,7 @@ typedef enum NodeTag T_ReassignOwnedStmt, T_CompositeTypeStmt, T_CreateEnumStmt, + T_AlterEnumStmt, T_AlterTSDictionaryStmt, T_AlterTSConfigurationStmt, T_CreateFdwStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e0bdebd0ab..3cac54b947 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2193,6 +2193,18 @@ typedef struct CreateEnumStmt List *vals; /* enum values (list of Value strings) */ } CreateEnumStmt; +/* ---------------------- + * Alter Type Statement, enum types + * ---------------------- + */ +typedef struct AlterEnumStmt +{ + NodeTag type; + List *typeName; /* qualified name (list of Value strings) */ + char *newVal; /* new enum value's name */ + char *newValNeighbor; /* neighboring enum value, if specified */ + bool newValIsAfter; /* place new enum value after neighbor? */ +} AlterEnumStmt; /* ---------------------- * Create View Statement diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h index 4065e483e4..28b66718b3 100644 --- a/src/include/utils/typcache.h +++ b/src/include/utils/typcache.h @@ -20,6 +20,9 @@ #include "fmgr.h" +/* TypeCacheEnumData is an opaque struct known only within typcache.c */ +struct TypeCacheEnumData; + typedef struct TypeCacheEntry { /* typeId is the hash lookup key and MUST BE FIRST */ @@ -63,6 +66,12 @@ typedef struct TypeCacheEntry * reference-counted tupledesc.) */ TupleDesc tupDesc; + + /* + * Private information about an enum type. NULL if not enum or + * information hasn't been requested. + */ + struct TypeCacheEnumData *enumData; } TypeCacheEntry; /* Bit flags to indicate which fields a given caller needs to have set */ @@ -86,4 +95,6 @@ extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod); extern void assign_record_type_typmod(TupleDesc tupDesc); +extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2); + #endif /* TYPCACHE_H */ diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out index 56240c0e7a..b1ba3f1fad 100644 --- a/src/test/regress/expected/enum.out +++ b/src/test/regress/expected/enum.out @@ -24,6 +24,155 @@ SELECT 'mauve'::rainbow; ERROR: invalid input value for enum rainbow: "mauve" LINE 1: SELECT 'mauve'::rainbow; ^ +-- +-- adding new values +-- +CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' ); +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + enumlabel | enumsortorder +-----------+--------------- + venus | 1 + earth | 2 + mars | 3 +(3 rows) + +ALTER TYPE planets ADD 'uranus'; +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + enumlabel | enumsortorder +-----------+--------------- + venus | 1 + earth | 2 + mars | 3 + uranus | 4 +(4 rows) + +ALTER TYPE planets ADD 'mercury' BEFORE 'venus'; +ALTER TYPE planets ADD 'saturn' BEFORE 'uranus'; +ALTER TYPE planets ADD 'jupiter' AFTER 'mars'; +ALTER TYPE planets ADD 'neptune' AFTER 'uranus'; +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + enumlabel | enumsortorder +-----------+--------------- + mercury | 0 + venus | 1 + earth | 2 + mars | 3 + jupiter | 3.25 + saturn | 3.5 + uranus | 4 + neptune | 5 +(8 rows) + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY enumlabel::planets; + enumlabel | enumsortorder +-----------+--------------- + mercury | 0 + venus | 1 + earth | 2 + mars | 3 + jupiter | 3.25 + saturn | 3.5 + uranus | 4 + neptune | 5 +(8 rows) + +-- errors for adding labels +ALTER TYPE planets ADD + 'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto'; +ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto" +DETAIL: Labels must be 63 characters or less. +ALTER TYPE planets ADD 'pluto' AFTER 'zeus'; +ERROR: "zeus" is not an existing enum label +-- +-- Test inserting so many values that we have to renumber +-- +create type insenum as enum ('L1', 'L2'); +alter type insenum add 'i1' before 'L2'; +alter type insenum add 'i2' before 'L2'; +alter type insenum add 'i3' before 'L2'; +alter type insenum add 'i4' before 'L2'; +alter type insenum add 'i5' before 'L2'; +alter type insenum add 'i6' before 'L2'; +alter type insenum add 'i7' before 'L2'; +alter type insenum add 'i8' before 'L2'; +alter type insenum add 'i9' before 'L2'; +alter type insenum add 'i10' before 'L2'; +alter type insenum add 'i11' before 'L2'; +alter type insenum add 'i12' before 'L2'; +alter type insenum add 'i13' before 'L2'; +alter type insenum add 'i14' before 'L2'; +alter type insenum add 'i15' before 'L2'; +alter type insenum add 'i16' before 'L2'; +alter type insenum add 'i17' before 'L2'; +alter type insenum add 'i18' before 'L2'; +alter type insenum add 'i19' before 'L2'; +alter type insenum add 'i20' before 'L2'; +alter type insenum add 'i21' before 'L2'; +alter type insenum add 'i22' before 'L2'; +alter type insenum add 'i23' before 'L2'; +alter type insenum add 'i24' before 'L2'; +alter type insenum add 'i25' before 'L2'; +alter type insenum add 'i26' before 'L2'; +alter type insenum add 'i27' before 'L2'; +alter type insenum add 'i28' before 'L2'; +alter type insenum add 'i29' before 'L2'; +alter type insenum add 'i30' before 'L2'; +-- The exact values of enumsortorder will now depend on the local properties +-- of float4, but in any reasonable implementation we should get at least +-- 20 splits before having to renumber; so only hide values > 20. +SELECT enumlabel, + case when enumsortorder > 20 then null else enumsortorder end as so +FROM pg_enum +WHERE enumtypid = 'insenum'::regtype +ORDER BY enumsortorder; + enumlabel | so +-----------+---- + L1 | 1 + i1 | 2 + i2 | 3 + i3 | 4 + i4 | 5 + i5 | 6 + i6 | 7 + i7 | 8 + i8 | 9 + i9 | 10 + i10 | 11 + i11 | 12 + i12 | 13 + i13 | 14 + i14 | 15 + i15 | 16 + i16 | 17 + i17 | 18 + i18 | 19 + i19 | 20 + i20 | + i21 | + i22 | + i23 | + i24 | + i25 | + i26 | + i27 | + i28 | + i29 | + i30 | + L2 | +(32 rows) + -- -- Basic table creation, row selection -- @@ -403,7 +552,7 @@ SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow'; SELECT * FROM pg_enum WHERE NOT EXISTS (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid); - enumtypid | enumlabel ------------+----------- + enumtypid | enumsortorder | enumlabel +-----------+---------------+----------- (0 rows) diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql index 387e8e72ed..70bf8c5f1b 100644 --- a/src/test/regress/sql/enum.sql +++ b/src/test/regress/sql/enum.sql @@ -15,6 +15,92 @@ SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype; SELECT 'red'::rainbow; SELECT 'mauve'::rainbow; +-- +-- adding new values +-- + +CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' ); + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +ALTER TYPE planets ADD 'uranus'; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +ALTER TYPE planets ADD 'mercury' BEFORE 'venus'; +ALTER TYPE planets ADD 'saturn' BEFORE 'uranus'; +ALTER TYPE planets ADD 'jupiter' AFTER 'mars'; +ALTER TYPE planets ADD 'neptune' AFTER 'uranus'; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY 2; + +SELECT enumlabel, enumsortorder +FROM pg_enum +WHERE enumtypid = 'planets'::regtype +ORDER BY enumlabel::planets; + +-- errors for adding labels +ALTER TYPE planets ADD + 'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto'; + +ALTER TYPE planets ADD 'pluto' AFTER 'zeus'; + +-- +-- Test inserting so many values that we have to renumber +-- + +create type insenum as enum ('L1', 'L2'); + +alter type insenum add 'i1' before 'L2'; +alter type insenum add 'i2' before 'L2'; +alter type insenum add 'i3' before 'L2'; +alter type insenum add 'i4' before 'L2'; +alter type insenum add 'i5' before 'L2'; +alter type insenum add 'i6' before 'L2'; +alter type insenum add 'i7' before 'L2'; +alter type insenum add 'i8' before 'L2'; +alter type insenum add 'i9' before 'L2'; +alter type insenum add 'i10' before 'L2'; +alter type insenum add 'i11' before 'L2'; +alter type insenum add 'i12' before 'L2'; +alter type insenum add 'i13' before 'L2'; +alter type insenum add 'i14' before 'L2'; +alter type insenum add 'i15' before 'L2'; +alter type insenum add 'i16' before 'L2'; +alter type insenum add 'i17' before 'L2'; +alter type insenum add 'i18' before 'L2'; +alter type insenum add 'i19' before 'L2'; +alter type insenum add 'i20' before 'L2'; +alter type insenum add 'i21' before 'L2'; +alter type insenum add 'i22' before 'L2'; +alter type insenum add 'i23' before 'L2'; +alter type insenum add 'i24' before 'L2'; +alter type insenum add 'i25' before 'L2'; +alter type insenum add 'i26' before 'L2'; +alter type insenum add 'i27' before 'L2'; +alter type insenum add 'i28' before 'L2'; +alter type insenum add 'i29' before 'L2'; +alter type insenum add 'i30' before 'L2'; + +-- The exact values of enumsortorder will now depend on the local properties +-- of float4, but in any reasonable implementation we should get at least +-- 20 splits before having to renumber; so only hide values > 20. + +SELECT enumlabel, + case when enumsortorder > 20 then null else enumsortorder end as so +FROM pg_enum +WHERE enumtypid = 'insenum'::regtype +ORDER BY enumsortorder; + -- -- Basic table creation, row selection --