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