summaryrefslogtreecommitdiffstats
path: root/src/backend/catalog/pg_enum.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/catalog/pg_enum.c')
-rw-r--r--src/backend/catalog/pg_enum.c761
1 files changed, 761 insertions, 0 deletions
diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c
new file mode 100644
index 0000000..f958f15
--- /dev/null
+++ b/src/backend/catalog/pg_enum.c
@@ -0,0 +1,761 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_enum.c
+ * routines to support manipulation of the pg_enum relation
+ *
+ * Copyright (c) 2006-2021, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_enum.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/binary_upgrade.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_enum.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/value.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+/* Potentially set by pg_upgrade_support functions */
+Oid binary_upgrade_next_pg_enum_oid = InvalidOid;
+
+/*
+ * Hash table of enum value OIDs created during the current transaction by
+ * AddEnumLabel. We disallow using these values until the transaction is
+ * committed; otherwise, they might get into indexes where we can't clean
+ * them up, and then if the transaction rolls back we have a broken index.
+ * (See comments for check_safe_enum_use() in enum.c.) Values created by
+ * EnumValuesCreate are *not* entered into the table; we assume those are
+ * created during CREATE TYPE, so they can't go away unless the enum type
+ * itself does.
+ */
+static HTAB *uncommitted_enums = NULL;
+
+static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
+static int sort_order_cmp(const void *p1, const void *p2);
+
+
+/*
+ * EnumValuesCreate
+ * Create an entry in pg_enum for each of the supplied enum values.
+ *
+ * vals is a list of Value strings.
+ */
+void
+EnumValuesCreate(Oid enumTypeOid, List *vals)
+{
+ Relation pg_enum;
+ NameData enumlabel;
+ Oid *oids;
+ int elemno,
+ num_elems;
+ Datum values[Natts_pg_enum];
+ bool nulls[Natts_pg_enum];
+ ListCell *lc;
+ HeapTuple tup;
+
+ num_elems = list_length(vals);
+
+ /*
+ * 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. It is
+ * probably not worth trying harder.
+ */
+
+ pg_enum = table_open(EnumRelationId, RowExclusiveLock);
+
+ /*
+ * 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));
+
+ for (elemno = 0; elemno < num_elems; elemno++)
+ {
+ /*
+ * 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.
+ */
+ Oid new_oid;
+
+ do
+ {
+ new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
+ Anum_pg_enum_oid);
+ } 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));
+
+ elemno = 0;
+ foreach(lc, vals)
+ {
+ char *lab = strVal(lfirst(lc));
+
+ /*
+ * labels are stored in a name field, for easier syscache lookup, so
+ * check the length to make sure it's within range.
+ */
+ if (strlen(lab) > (NAMEDATALEN - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("invalid enum label \"%s\"", lab),
+ errdetail("Labels must be %d bytes or less.",
+ NAMEDATALEN - 1)));
+
+ values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
+ 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(RelationGetDescr(pg_enum), values, nulls);
+
+ CatalogTupleInsert(pg_enum, tup);
+ heap_freetuple(tup);
+
+ elemno++;
+ }
+
+ /* clean up */
+ pfree(oids);
+ table_close(pg_enum, RowExclusiveLock);
+}
+
+
+/*
+ * EnumValuesDelete
+ * Remove all the pg_enum entries for the specified enum type.
+ */
+void
+EnumValuesDelete(Oid enumTypeOid)
+{
+ Relation pg_enum;
+ ScanKeyData key[1];
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ pg_enum = table_open(EnumRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_enum_enumtypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(enumTypeOid));
+
+ scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
+ NULL, 1, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ CatalogTupleDelete(pg_enum, &tup->t_self);
+ }
+
+ systable_endscan(scan);
+
+ table_close(pg_enum, RowExclusiveLock);
+}
+
+/*
+ * Initialize the uncommitted enum table for this transaction.
+ */
+static void
+init_uncommitted_enums(void)
+{
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(Oid);
+ hash_ctl.hcxt = TopTransactionContext;
+ uncommitted_enums = hash_create("Uncommitted enums",
+ 32,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/*
+ * 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,
+ bool skipIfExists)
+{
+ 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 bytes 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);
+
+ /*
+ * Check if label is already in use. The unique index on pg_enum would
+ * catch this anyway, but we prefer a friendlier error message, and
+ * besides we need a check to support IF NOT EXISTS.
+ */
+ enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
+ ObjectIdGetDatum(enumTypeOid),
+ CStringGetDatum(newVal));
+ if (HeapTupleIsValid(enum_tup))
+ {
+ ReleaseSysCache(enum_tup);
+ if (skipIfExists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("enum label \"%s\" already exists, skipping",
+ newVal)));
+ return;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("enum label \"%s\" already exists",
+ newVal)));
+ }
+
+ pg_enum = table_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
+ {
+ /*
+ * The midpoint value computed here has to be rounded to float4
+ * precision, else our equality comparisons against the adjacent
+ * values are meaningless. The most portable way of forcing that
+ * to happen with non-C-standard-compliant compilers is to store
+ * it into a volatile variable.
+ */
+ volatile float4 midpoint;
+
+ other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
+ midpoint = (nbr_en->enumsortorder +
+ other_nbr_en->enumsortorder) / 2;
+
+ if (midpoint == nbr_en->enumsortorder ||
+ midpoint == other_nbr_en->enumsortorder)
+ {
+ RenumberEnumType(pg_enum, existing, nelems);
+ /* Clean up and start over */
+ pfree(existing);
+ ReleaseCatCacheList(list);
+ goto restart;
+ }
+
+ newelemorder = midpoint;
+ }
+ }
+
+ /* Get a new OID for the new label */
+ if (IsBinaryUpgrade)
+ {
+ if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("pg_enum OID value not set when in binary upgrade mode")));
+
+ /*
+ * Use binary-upgrade override for pg_enum.oid, if supplied. During
+ * binary upgrade, all pg_enum.oid's are set this way so they are
+ * guaranteed to be consistent.
+ */
+ 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 = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
+ Anum_pg_enum_oid);
+
+ /*
+ * 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 = exists_en->oid;
+
+ 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_oid - 1] = ObjectIdGetDatum(newOid);
+ 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);
+ CatalogTupleInsert(pg_enum, enum_tup);
+ heap_freetuple(enum_tup);
+
+ table_close(pg_enum, RowExclusiveLock);
+
+ /* Set up the uncommitted enum table if not already done in this tx */
+ if (uncommitted_enums == NULL)
+ init_uncommitted_enums();
+
+ /* Add the new value to the table */
+ (void) hash_search(uncommitted_enums, &newOid, HASH_ENTER, NULL);
+}
+
+
+/*
+ * 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 bytes 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 = table_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);
+ CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
+ heap_freetuple(enum_tup);
+
+ table_close(pg_enum, RowExclusiveLock);
+}
+
+
+/*
+ * Test if the given enum value is in the table of uncommitted enums.
+ */
+bool
+EnumUncommitted(Oid enum_id)
+{
+ bool found;
+
+ /* If we've made no uncommitted table, all values are safe */
+ if (uncommitted_enums == NULL)
+ return false;
+
+ /* Else, is it in the table? */
+ (void) hash_search(uncommitted_enums, &enum_id, HASH_FIND, &found);
+ return found;
+}
+
+
+/*
+ * Clean up enum stuff after end of top-level transaction.
+ */
+void
+AtEOXact_Enum(void)
+{
+ /*
+ * Reset the uncommitted table, as all our enum values are now committed.
+ * The memory will go away automatically when TopTransactionContext is
+ * freed; it's sufficient to clear our pointer.
+ */
+ uncommitted_enums = NULL;
+}
+
+
+/*
+ * 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. Although system catalog scans now use MVCC semantics, the
+ * syscache machinery might read different pg_enum entries under different
+ * snapshots, so some other backend might get confused about the proper
+ * ordering if a concurrent renumbering occurs.
+ *
+ * We therefore make the following choices:
+ *
+ * 1. Any code that is interested in the enumsortorder values MUST read
+ * all the relevant pg_enum entries with a single MVCC snapshot, or else
+ * acquire lock on the enum type to prevent concurrent execution of
+ * AddEnumLabel().
+ *
+ * 2. Code that is not examining enumsortorder can use a syscache
+ * (for example, enum_in and enum_out do so).
+ */
+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;
+
+ CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
+ }
+
+ heap_freetuple(newtup);
+ }
+
+ /* Make the updates visible */
+ CommandCounterIncrement();
+}
+
+
+/* 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;
+}
+
+Size
+EstimateUncommittedEnumsSpace(void)
+{
+ size_t entries;
+
+ if (uncommitted_enums)
+ entries = hash_get_num_entries(uncommitted_enums);
+ else
+ entries = 0;
+
+ /* Add one for the terminator. */
+ return sizeof(Oid) * (entries + 1);
+}
+
+void
+SerializeUncommittedEnums(void *space, Size size)
+{
+ Oid *serialized = (Oid *) space;
+
+ /*
+ * Make sure the hash table hasn't changed in size since the caller
+ * reserved the space.
+ */
+ Assert(size == EstimateUncommittedEnumsSpace());
+
+ /* Write out all the values from the hash table, if there is one. */
+ if (uncommitted_enums)
+ {
+ HASH_SEQ_STATUS status;
+ Oid *value;
+
+ hash_seq_init(&status, uncommitted_enums);
+ while ((value = (Oid *) hash_seq_search(&status)))
+ *serialized++ = *value;
+ }
+
+ /* Write out the terminator. */
+ *serialized = InvalidOid;
+
+ /*
+ * Make sure the amount of space we actually used matches what was
+ * estimated.
+ */
+ Assert((char *) (serialized + 1) == ((char *) space) + size);
+}
+
+void
+RestoreUncommittedEnums(void *space)
+{
+ Oid *serialized = (Oid *) space;
+
+ Assert(!uncommitted_enums);
+
+ /*
+ * As a special case, if the list is empty then don't even bother to
+ * create the hash table. This is the usual case, since enum alteration
+ * is expected to be rare.
+ */
+ if (!OidIsValid(*serialized))
+ return;
+
+ /* Read all the values into a new hash table. */
+ init_uncommitted_enums();
+ do
+ {
+ hash_search(uncommitted_enums, serialized++, HASH_ENTER, NULL);
+ } while (OidIsValid(*serialized));
+}