| /*------------------------------------------------------------------------- |
| * |
| * 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 "cdb/cdbvars.h" |
| #include "storage/lmgr.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" |
| |
| #include "catalog/oid_dispatch.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)); |
| |
| elemno = 0; |
| foreach(lc, vals) |
| { |
| /* |
| * 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. |
| */ |
| char *lab = strVal(lfirst(lc)); |
| Oid new_oid; |
| |
| do |
| { |
| /* |
| * In QE node, however, use the OIDs assigned by the master (they are delivered |
| * out-of-band, see oid_dispatch.c. |
| */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| { |
| new_oid = GetPreassignedOidForEnum(enumTypeOid, lab); |
| break; |
| } |
| |
| new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId, |
| Anum_pg_enum_oid); |
| } while (new_oid & 1); |
| oids[elemno] = new_oid; |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| RememberAssignedOidForEnum(enumTypeOid, lab, new_oid); |
| |
| elemno++; |
| } |
| Assert(elemno == num_elems); |
| |
| /* 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 (Gp_role == GP_ROLE_EXECUTE || IsBinaryUpgrade) |
| { |
| /* |
| * In QE, the dispatcher has already allocated the OID for us. |
| */ |
| newOid = GetPreassignedOidForEnum(enumTypeOid, newVal); |
| } |
| 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.) |
| */ |
| } |
| } |
| if (Gp_role == GP_ROLE_DISPATCH) |
| RememberAssignedOidForEnum(enumTypeOid, newVal, newOid); |
| } |
| |
| /* 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)); |
| } |