1/*-------------------------------------------------------------------------
2 *
3 * pg_enum.c
4 * routines to support manipulation of the pg_enum relation
5 *
6 * Copyright (c) 2006-2019, PostgreSQL Global Development Group
7 *
8 *
9 * IDENTIFICATION
10 * src/backend/catalog/pg_enum.c
11 *
12 *-------------------------------------------------------------------------
13 */
14#include "postgres.h"
15
16#include "access/genam.h"
17#include "access/htup_details.h"
18#include "access/table.h"
19#include "access/xact.h"
20#include "catalog/binary_upgrade.h"
21#include "catalog/catalog.h"
22#include "catalog/indexing.h"
23#include "catalog/pg_enum.h"
24#include "catalog/pg_type.h"
25#include "storage/lmgr.h"
26#include "miscadmin.h"
27#include "nodes/value.h"
28#include "utils/builtins.h"
29#include "utils/catcache.h"
30#include "utils/fmgroids.h"
31#include "utils/hsearch.h"
32#include "utils/memutils.h"
33#include "utils/syscache.h"
34
35
36/* Potentially set by pg_upgrade_support functions */
37Oid binary_upgrade_next_pg_enum_oid = InvalidOid;
38
39/*
40 * Hash table of enum value OIDs created during the current transaction by
41 * AddEnumLabel. We disallow using these values until the transaction is
42 * committed; otherwise, they might get into indexes where we can't clean
43 * them up, and then if the transaction rolls back we have a broken index.
44 * (See comments for check_safe_enum_use() in enum.c.) Values created by
45 * EnumValuesCreate are *not* blacklisted; we assume those are created during
46 * CREATE TYPE, so they can't go away unless the enum type itself does.
47 */
48static HTAB *enum_blacklist = NULL;
49
50static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
51static int sort_order_cmp(const void *p1, const void *p2);
52
53
54/*
55 * EnumValuesCreate
56 * Create an entry in pg_enum for each of the supplied enum values.
57 *
58 * vals is a list of Value strings.
59 */
60void
61EnumValuesCreate(Oid enumTypeOid, List *vals)
62{
63 Relation pg_enum;
64 NameData enumlabel;
65 Oid *oids;
66 int elemno,
67 num_elems;
68 Datum values[Natts_pg_enum];
69 bool nulls[Natts_pg_enum];
70 ListCell *lc;
71 HeapTuple tup;
72
73 num_elems = list_length(vals);
74
75 /*
76 * We do not bother to check the list of values for duplicates --- if you
77 * have any, you'll get a less-than-friendly unique-index violation. It is
78 * probably not worth trying harder.
79 */
80
81 pg_enum = table_open(EnumRelationId, RowExclusiveLock);
82
83 /*
84 * Allocate OIDs for the enum's members.
85 *
86 * While this method does not absolutely guarantee that we generate no
87 * duplicate OIDs (since we haven't entered each oid into the table before
88 * allocating the next), trouble could only occur if the OID counter wraps
89 * all the way around before we finish. Which seems unlikely.
90 */
91 oids = (Oid *) palloc(num_elems * sizeof(Oid));
92
93 for (elemno = 0; elemno < num_elems; elemno++)
94 {
95 /*
96 * We assign even-numbered OIDs to all the new enum labels. This
97 * tells the comparison functions the OIDs are in the correct sort
98 * order and can be compared directly.
99 */
100 Oid new_oid;
101
102 do
103 {
104 new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
105 Anum_pg_enum_oid);
106 } while (new_oid & 1);
107 oids[elemno] = new_oid;
108 }
109
110 /* sort them, just in case OID counter wrapped from high to low */
111 qsort(oids, num_elems, sizeof(Oid), oid_cmp);
112
113 /* and make the entries */
114 memset(nulls, false, sizeof(nulls));
115
116 elemno = 0;
117 foreach(lc, vals)
118 {
119 char *lab = strVal(lfirst(lc));
120
121 /*
122 * labels are stored in a name field, for easier syscache lookup, so
123 * check the length to make sure it's within range.
124 */
125 if (strlen(lab) > (NAMEDATALEN - 1))
126 ereport(ERROR,
127 (errcode(ERRCODE_INVALID_NAME),
128 errmsg("invalid enum label \"%s\"", lab),
129 errdetail("Labels must be %d characters or less.",
130 NAMEDATALEN - 1)));
131
132 values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
133 values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
134 values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
135 namestrcpy(&enumlabel, lab);
136 values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
137
138 tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
139
140 CatalogTupleInsert(pg_enum, tup);
141 heap_freetuple(tup);
142
143 elemno++;
144 }
145
146 /* clean up */
147 pfree(oids);
148 table_close(pg_enum, RowExclusiveLock);
149}
150
151
152/*
153 * EnumValuesDelete
154 * Remove all the pg_enum entries for the specified enum type.
155 */
156void
157EnumValuesDelete(Oid enumTypeOid)
158{
159 Relation pg_enum;
160 ScanKeyData key[1];
161 SysScanDesc scan;
162 HeapTuple tup;
163
164 pg_enum = table_open(EnumRelationId, RowExclusiveLock);
165
166 ScanKeyInit(&key[0],
167 Anum_pg_enum_enumtypid,
168 BTEqualStrategyNumber, F_OIDEQ,
169 ObjectIdGetDatum(enumTypeOid));
170
171 scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
172 NULL, 1, key);
173
174 while (HeapTupleIsValid(tup = systable_getnext(scan)))
175 {
176 CatalogTupleDelete(pg_enum, &tup->t_self);
177 }
178
179 systable_endscan(scan);
180
181 table_close(pg_enum, RowExclusiveLock);
182}
183
184/*
185 * Initialize the enum blacklist for this transaction.
186 */
187static void
188init_enum_blacklist(void)
189{
190 HASHCTL hash_ctl;
191
192 memset(&hash_ctl, 0, sizeof(hash_ctl));
193 hash_ctl.keysize = sizeof(Oid);
194 hash_ctl.entrysize = sizeof(Oid);
195 hash_ctl.hcxt = TopTransactionContext;
196 enum_blacklist = hash_create("Enum value blacklist",
197 32,
198 &hash_ctl,
199 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
200}
201
202/*
203 * AddEnumLabel
204 * Add a new label to the enum set. By default it goes at
205 * the end, but the user can choose to place it before or
206 * after any existing set member.
207 */
208void
209AddEnumLabel(Oid enumTypeOid,
210 const char *newVal,
211 const char *neighbor,
212 bool newValIsAfter,
213 bool skipIfExists)
214{
215 Relation pg_enum;
216 Oid newOid;
217 Datum values[Natts_pg_enum];
218 bool nulls[Natts_pg_enum];
219 NameData enumlabel;
220 HeapTuple enum_tup;
221 float4 newelemorder;
222 HeapTuple *existing;
223 CatCList *list;
224 int nelems;
225 int i;
226
227 /* check length of new label is ok */
228 if (strlen(newVal) > (NAMEDATALEN - 1))
229 ereport(ERROR,
230 (errcode(ERRCODE_INVALID_NAME),
231 errmsg("invalid enum label \"%s\"", newVal),
232 errdetail("Labels must be %d characters or less.",
233 NAMEDATALEN - 1)));
234
235 /*
236 * Acquire a lock on the enum type, which we won't release until commit.
237 * This ensures that two backends aren't concurrently modifying the same
238 * enum type. Without that, we couldn't be sure to get a consistent view
239 * of the enum members via the syscache. Note that this does not block
240 * other backends from inspecting the type; see comments for
241 * RenumberEnumType.
242 */
243 LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
244
245 /*
246 * Check if label is already in use. The unique index on pg_enum would
247 * catch this anyway, but we prefer a friendlier error message, and
248 * besides we need a check to support IF NOT EXISTS.
249 */
250 enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
251 ObjectIdGetDatum(enumTypeOid),
252 CStringGetDatum(newVal));
253 if (HeapTupleIsValid(enum_tup))
254 {
255 ReleaseSysCache(enum_tup);
256 if (skipIfExists)
257 {
258 ereport(NOTICE,
259 (errcode(ERRCODE_DUPLICATE_OBJECT),
260 errmsg("enum label \"%s\" already exists, skipping",
261 newVal)));
262 return;
263 }
264 else
265 ereport(ERROR,
266 (errcode(ERRCODE_DUPLICATE_OBJECT),
267 errmsg("enum label \"%s\" already exists",
268 newVal)));
269 }
270
271 pg_enum = table_open(EnumRelationId, RowExclusiveLock);
272
273 /* If we have to renumber the existing members, we restart from here */
274restart:
275
276 /* Get the list of existing members of the enum */
277 list = SearchSysCacheList1(ENUMTYPOIDNAME,
278 ObjectIdGetDatum(enumTypeOid));
279 nelems = list->n_members;
280
281 /* Sort the existing members by enumsortorder */
282 existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
283 for (i = 0; i < nelems; i++)
284 existing[i] = &(list->members[i]->tuple);
285
286 qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
287
288 if (neighbor == NULL)
289 {
290 /*
291 * Put the new label at the end of the list. No change to existing
292 * tuples is required.
293 */
294 if (nelems > 0)
295 {
296 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
297
298 newelemorder = en->enumsortorder + 1;
299 }
300 else
301 newelemorder = 1;
302 }
303 else
304 {
305 /* BEFORE or AFTER was specified */
306 int nbr_index;
307 int other_nbr_index;
308 Form_pg_enum nbr_en;
309 Form_pg_enum other_nbr_en;
310
311 /* Locate the neighbor element */
312 for (nbr_index = 0; nbr_index < nelems; nbr_index++)
313 {
314 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
315
316 if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
317 break;
318 }
319 if (nbr_index >= nelems)
320 ereport(ERROR,
321 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
322 errmsg("\"%s\" is not an existing enum label",
323 neighbor)));
324 nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
325
326 /*
327 * Attempt to assign an appropriate enumsortorder value: one less than
328 * the smallest member, one more than the largest member, or halfway
329 * between two existing members.
330 *
331 * In the "halfway" case, because of the finite precision of float4,
332 * we might compute a value that's actually equal to one or the other
333 * of its neighbors. In that case we renumber the existing members
334 * and try again.
335 */
336 if (newValIsAfter)
337 other_nbr_index = nbr_index + 1;
338 else
339 other_nbr_index = nbr_index - 1;
340
341 if (other_nbr_index < 0)
342 newelemorder = nbr_en->enumsortorder - 1;
343 else if (other_nbr_index >= nelems)
344 newelemorder = nbr_en->enumsortorder + 1;
345 else
346 {
347 /*
348 * The midpoint value computed here has to be rounded to float4
349 * precision, else our equality comparisons against the adjacent
350 * values are meaningless. The most portable way of forcing that
351 * to happen with non-C-standard-compliant compilers is to store
352 * it into a volatile variable.
353 */
354 volatile float4 midpoint;
355
356 other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
357 midpoint = (nbr_en->enumsortorder +
358 other_nbr_en->enumsortorder) / 2;
359
360 if (midpoint == nbr_en->enumsortorder ||
361 midpoint == other_nbr_en->enumsortorder)
362 {
363 RenumberEnumType(pg_enum, existing, nelems);
364 /* Clean up and start over */
365 pfree(existing);
366 ReleaseCatCacheList(list);
367 goto restart;
368 }
369
370 newelemorder = midpoint;
371 }
372 }
373
374 /* Get a new OID for the new label */
375 if (IsBinaryUpgrade)
376 {
377 if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
378 ereport(ERROR,
379 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
380 errmsg("pg_enum OID value not set when in binary upgrade mode")));
381
382 /*
383 * Use binary-upgrade override for pg_enum.oid, if supplied. During
384 * binary upgrade, all pg_enum.oid's are set this way so they are
385 * guaranteed to be consistent.
386 */
387 if (neighbor != NULL)
388 ereport(ERROR,
389 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
390 errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
391
392 newOid = binary_upgrade_next_pg_enum_oid;
393 binary_upgrade_next_pg_enum_oid = InvalidOid;
394 }
395 else
396 {
397 /*
398 * Normal case: we need to allocate a new Oid for the value.
399 *
400 * We want to give the new element an even-numbered Oid if it's safe,
401 * which is to say it compares correctly to all pre-existing even
402 * numbered Oids in the enum. Otherwise, we must give it an odd Oid.
403 */
404 for (;;)
405 {
406 bool sorts_ok;
407
408 /* Get a new OID (different from all existing pg_enum tuples) */
409 newOid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
410 Anum_pg_enum_oid);
411
412 /*
413 * Detect whether it sorts correctly relative to existing
414 * even-numbered labels of the enum. We can ignore existing
415 * labels with odd Oids, since a comparison involving one of those
416 * will not take the fast path anyway.
417 */
418 sorts_ok = true;
419 for (i = 0; i < nelems; i++)
420 {
421 HeapTuple exists_tup = existing[i];
422 Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
423 Oid exists_oid = exists_en->oid;
424
425 if (exists_oid & 1)
426 continue; /* ignore odd Oids */
427
428 if (exists_en->enumsortorder < newelemorder)
429 {
430 /* should sort before */
431 if (exists_oid >= newOid)
432 {
433 sorts_ok = false;
434 break;
435 }
436 }
437 else
438 {
439 /* should sort after */
440 if (exists_oid <= newOid)
441 {
442 sorts_ok = false;
443 break;
444 }
445 }
446 }
447
448 if (sorts_ok)
449 {
450 /* If it's even and sorts OK, we're done. */
451 if ((newOid & 1) == 0)
452 break;
453
454 /*
455 * If it's odd, and sorts OK, loop back to get another OID and
456 * try again. Probably, the next available even OID will sort
457 * correctly too, so it's worth trying.
458 */
459 }
460 else
461 {
462 /*
463 * If it's odd, and does not sort correctly, we're done.
464 * (Probably, the next available even OID would sort
465 * incorrectly too, so no point in trying again.)
466 */
467 if (newOid & 1)
468 break;
469
470 /*
471 * If it's even, and does not sort correctly, loop back to get
472 * another OID and try again. (We *must* reject this case.)
473 */
474 }
475 }
476 }
477
478 /* Done with info about existing members */
479 pfree(existing);
480 ReleaseCatCacheList(list);
481
482 /* Create the new pg_enum entry */
483 memset(nulls, false, sizeof(nulls));
484 values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
485 values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
486 values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
487 namestrcpy(&enumlabel, newVal);
488 values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
489 enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
490 CatalogTupleInsert(pg_enum, enum_tup);
491 heap_freetuple(enum_tup);
492
493 table_close(pg_enum, RowExclusiveLock);
494
495 /* Set up the blacklist hash if not already done in this transaction */
496 if (enum_blacklist == NULL)
497 init_enum_blacklist();
498
499 /* Add the new value to the blacklist */
500 (void) hash_search(enum_blacklist, &newOid, HASH_ENTER, NULL);
501}
502
503
504/*
505 * RenameEnumLabel
506 * Rename a label in an enum set.
507 */
508void
509RenameEnumLabel(Oid enumTypeOid,
510 const char *oldVal,
511 const char *newVal)
512{
513 Relation pg_enum;
514 HeapTuple enum_tup;
515 Form_pg_enum en;
516 CatCList *list;
517 int nelems;
518 HeapTuple old_tup;
519 bool found_new;
520 int i;
521
522 /* check length of new label is ok */
523 if (strlen(newVal) > (NAMEDATALEN - 1))
524 ereport(ERROR,
525 (errcode(ERRCODE_INVALID_NAME),
526 errmsg("invalid enum label \"%s\"", newVal),
527 errdetail("Labels must be %d characters or less.",
528 NAMEDATALEN - 1)));
529
530 /*
531 * Acquire a lock on the enum type, which we won't release until commit.
532 * This ensures that two backends aren't concurrently modifying the same
533 * enum type. Since we are not changing the type's sort order, this is
534 * probably not really necessary, but there seems no reason not to take
535 * the lock to be sure.
536 */
537 LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
538
539 pg_enum = table_open(EnumRelationId, RowExclusiveLock);
540
541 /* Get the list of existing members of the enum */
542 list = SearchSysCacheList1(ENUMTYPOIDNAME,
543 ObjectIdGetDatum(enumTypeOid));
544 nelems = list->n_members;
545
546 /*
547 * Locate the element to rename and check if the new label is already in
548 * use. (The unique index on pg_enum would catch that anyway, but we
549 * prefer a friendlier error message.)
550 */
551 old_tup = NULL;
552 found_new = false;
553 for (i = 0; i < nelems; i++)
554 {
555 enum_tup = &(list->members[i]->tuple);
556 en = (Form_pg_enum) GETSTRUCT(enum_tup);
557 if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
558 old_tup = enum_tup;
559 if (strcmp(NameStr(en->enumlabel), newVal) == 0)
560 found_new = true;
561 }
562 if (!old_tup)
563 ereport(ERROR,
564 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
565 errmsg("\"%s\" is not an existing enum label",
566 oldVal)));
567 if (found_new)
568 ereport(ERROR,
569 (errcode(ERRCODE_DUPLICATE_OBJECT),
570 errmsg("enum label \"%s\" already exists",
571 newVal)));
572
573 /* OK, make a writable copy of old tuple */
574 enum_tup = heap_copytuple(old_tup);
575 en = (Form_pg_enum) GETSTRUCT(enum_tup);
576
577 ReleaseCatCacheList(list);
578
579 /* Update the pg_enum entry */
580 namestrcpy(&en->enumlabel, newVal);
581 CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
582 heap_freetuple(enum_tup);
583
584 table_close(pg_enum, RowExclusiveLock);
585}
586
587
588/*
589 * Test if the given enum value is on the blacklist
590 */
591bool
592EnumBlacklisted(Oid enum_id)
593{
594 bool found;
595
596 /* If we've made no blacklist table, all values are safe */
597 if (enum_blacklist == NULL)
598 return false;
599
600 /* Else, is it in the table? */
601 (void) hash_search(enum_blacklist, &enum_id, HASH_FIND, &found);
602 return found;
603}
604
605
606/*
607 * Clean up enum stuff after end of top-level transaction.
608 */
609void
610AtEOXact_Enum(void)
611{
612 /*
613 * Reset the blacklist table, as all our enum values are now committed.
614 * The memory will go away automatically when TopTransactionContext is
615 * freed; it's sufficient to clear our pointer.
616 */
617 enum_blacklist = NULL;
618}
619
620
621/*
622 * RenumberEnumType
623 * Renumber existing enum elements to have sort positions 1..n.
624 *
625 * We avoid doing this unless absolutely necessary; in most installations
626 * it will never happen. The reason is that updating existing pg_enum
627 * entries creates hazards for other backends that are concurrently reading
628 * pg_enum. Although system catalog scans now use MVCC semantics, the
629 * syscache machinery might read different pg_enum entries under different
630 * snapshots, so some other backend might get confused about the proper
631 * ordering if a concurrent renumbering occurs.
632 *
633 * We therefore make the following choices:
634 *
635 * 1. Any code that is interested in the enumsortorder values MUST read
636 * all the relevant pg_enum entries with a single MVCC snapshot, or else
637 * acquire lock on the enum type to prevent concurrent execution of
638 * AddEnumLabel().
639 *
640 * 2. Code that is not examining enumsortorder can use a syscache
641 * (for example, enum_in and enum_out do so).
642 */
643static void
644RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
645{
646 int i;
647
648 /*
649 * We should only need to increase existing elements' enumsortorders,
650 * never decrease them. Therefore, work from the end backwards, to avoid
651 * unwanted uniqueness violations.
652 */
653 for (i = nelems - 1; i >= 0; i--)
654 {
655 HeapTuple newtup;
656 Form_pg_enum en;
657 float4 newsortorder;
658
659 newtup = heap_copytuple(existing[i]);
660 en = (Form_pg_enum) GETSTRUCT(newtup);
661
662 newsortorder = i + 1;
663 if (en->enumsortorder != newsortorder)
664 {
665 en->enumsortorder = newsortorder;
666
667 CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
668 }
669
670 heap_freetuple(newtup);
671 }
672
673 /* Make the updates visible */
674 CommandCounterIncrement();
675}
676
677
678/* qsort comparison function for tuples by sort order */
679static int
680sort_order_cmp(const void *p1, const void *p2)
681{
682 HeapTuple v1 = *((const HeapTuple *) p1);
683 HeapTuple v2 = *((const HeapTuple *) p2);
684 Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
685 Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
686
687 if (en1->enumsortorder < en2->enumsortorder)
688 return -1;
689 else if (en1->enumsortorder > en2->enumsortorder)
690 return 1;
691 else
692 return 0;
693}
694
695Size
696EstimateEnumBlacklistSpace(void)
697{
698 size_t entries;
699
700 if (enum_blacklist)
701 entries = hash_get_num_entries(enum_blacklist);
702 else
703 entries = 0;
704
705 /* Add one for the terminator. */
706 return sizeof(Oid) * (entries + 1);
707}
708
709void
710SerializeEnumBlacklist(void *space, Size size)
711{
712 Oid *serialized = (Oid *) space;
713
714 /*
715 * Make sure the hash table hasn't changed in size since the caller
716 * reserved the space.
717 */
718 Assert(size == EstimateEnumBlacklistSpace());
719
720 /* Write out all the values from the hash table, if there is one. */
721 if (enum_blacklist)
722 {
723 HASH_SEQ_STATUS status;
724 Oid *value;
725
726 hash_seq_init(&status, enum_blacklist);
727 while ((value = (Oid *) hash_seq_search(&status)))
728 *serialized++ = *value;
729 }
730
731 /* Write out the terminator. */
732 *serialized = InvalidOid;
733
734 /*
735 * Make sure the amount of space we actually used matches what was
736 * estimated.
737 */
738 Assert((char *) (serialized + 1) == ((char *) space) + size);
739}
740
741void
742RestoreEnumBlacklist(void *space)
743{
744 Oid *serialized = (Oid *) space;
745
746 Assert(!enum_blacklist);
747
748 /*
749 * As a special case, if the list is empty then don't even bother to
750 * create the hash table. This is the usual case, since enum alteration
751 * is expected to be rare.
752 */
753 if (!OidIsValid(*serialized))
754 return;
755
756 /* Read all the values into a new hash table. */
757 init_enum_blacklist();
758 do
759 {
760 hash_search(enum_blacklist, serialized++, HASH_ENTER, NULL);
761 } while (OidIsValid(*serialized));
762}
763