1 /*-------------------------------------------------------------------------
2  *
3  * pg_enum.c
4  *	  routines to support manipulation of the pg_enum relation
5  *
6  * Copyright (c) 2006-2018, 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/heapam.h"
18 #include "access/htup_details.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/syscache.h"
32 #include "utils/tqual.h"
33 
34 
35 /* Potentially set by pg_upgrade_support functions */
36 Oid			binary_upgrade_next_pg_enum_oid = InvalidOid;
37 
38 static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
39 static int	sort_order_cmp(const void *p1, const void *p2);
40 
41 
42 /*
43  * EnumValuesCreate
44  *		Create an entry in pg_enum for each of the supplied enum values.
45  *
46  * vals is a list of Value strings.
47  */
48 void
EnumValuesCreate(Oid enumTypeOid,List * vals)49 EnumValuesCreate(Oid enumTypeOid, List *vals)
50 {
51 	Relation	pg_enum;
52 	NameData	enumlabel;
53 	Oid		   *oids;
54 	int			elemno,
55 				num_elems;
56 	Datum		values[Natts_pg_enum];
57 	bool		nulls[Natts_pg_enum];
58 	ListCell   *lc;
59 	HeapTuple	tup;
60 
61 	num_elems = list_length(vals);
62 
63 	/*
64 	 * We do not bother to check the list of values for duplicates --- if you
65 	 * have any, you'll get a less-than-friendly unique-index violation. It is
66 	 * probably not worth trying harder.
67 	 */
68 
69 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
70 
71 	/*
72 	 * Allocate OIDs for the enum's members.
73 	 *
74 	 * While this method does not absolutely guarantee that we generate no
75 	 * duplicate OIDs (since we haven't entered each oid into the table before
76 	 * allocating the next), trouble could only occur if the OID counter wraps
77 	 * all the way around before we finish. Which seems unlikely.
78 	 */
79 	oids = (Oid *) palloc(num_elems * sizeof(Oid));
80 
81 	for (elemno = 0; elemno < num_elems; elemno++)
82 	{
83 		/*
84 		 * We assign even-numbered OIDs to all the new enum labels.  This
85 		 * tells the comparison functions the OIDs are in the correct sort
86 		 * order and can be compared directly.
87 		 */
88 		Oid			new_oid;
89 
90 		do
91 		{
92 			new_oid = GetNewOid(pg_enum);
93 		} while (new_oid & 1);
94 		oids[elemno] = new_oid;
95 	}
96 
97 	/* sort them, just in case OID counter wrapped from high to low */
98 	qsort(oids, num_elems, sizeof(Oid), oid_cmp);
99 
100 	/* and make the entries */
101 	memset(nulls, false, sizeof(nulls));
102 
103 	elemno = 0;
104 	foreach(lc, vals)
105 	{
106 		char	   *lab = strVal(lfirst(lc));
107 
108 		/*
109 		 * labels are stored in a name field, for easier syscache lookup, so
110 		 * check the length to make sure it's within range.
111 		 */
112 		if (strlen(lab) > (NAMEDATALEN - 1))
113 			ereport(ERROR,
114 					(errcode(ERRCODE_INVALID_NAME),
115 					 errmsg("invalid enum label \"%s\"", lab),
116 					 errdetail("Labels must be %d characters or less.",
117 							   NAMEDATALEN - 1)));
118 
119 		values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
120 		values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
121 		namestrcpy(&enumlabel, lab);
122 		values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
123 
124 		tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
125 		HeapTupleSetOid(tup, oids[elemno]);
126 
127 		CatalogTupleInsert(pg_enum, tup);
128 		heap_freetuple(tup);
129 
130 		elemno++;
131 	}
132 
133 	/* clean up */
134 	pfree(oids);
135 	heap_close(pg_enum, RowExclusiveLock);
136 }
137 
138 
139 /*
140  * EnumValuesDelete
141  *		Remove all the pg_enum entries for the specified enum type.
142  */
143 void
EnumValuesDelete(Oid enumTypeOid)144 EnumValuesDelete(Oid enumTypeOid)
145 {
146 	Relation	pg_enum;
147 	ScanKeyData key[1];
148 	SysScanDesc scan;
149 	HeapTuple	tup;
150 
151 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
152 
153 	ScanKeyInit(&key[0],
154 				Anum_pg_enum_enumtypid,
155 				BTEqualStrategyNumber, F_OIDEQ,
156 				ObjectIdGetDatum(enumTypeOid));
157 
158 	scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
159 							  NULL, 1, key);
160 
161 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
162 	{
163 		CatalogTupleDelete(pg_enum, &tup->t_self);
164 	}
165 
166 	systable_endscan(scan);
167 
168 	heap_close(pg_enum, RowExclusiveLock);
169 }
170 
171 
172 /*
173  * AddEnumLabel
174  *		Add a new label to the enum set. By default it goes at
175  *		the end, but the user can choose to place it before or
176  *		after any existing set member.
177  */
178 void
AddEnumLabel(Oid enumTypeOid,const char * newVal,const char * neighbor,bool newValIsAfter,bool skipIfExists)179 AddEnumLabel(Oid enumTypeOid,
180 			 const char *newVal,
181 			 const char *neighbor,
182 			 bool newValIsAfter,
183 			 bool skipIfExists)
184 {
185 	Relation	pg_enum;
186 	Oid			newOid;
187 	Datum		values[Natts_pg_enum];
188 	bool		nulls[Natts_pg_enum];
189 	NameData	enumlabel;
190 	HeapTuple	enum_tup;
191 	float4		newelemorder;
192 	HeapTuple  *existing;
193 	CatCList   *list;
194 	int			nelems;
195 	int			i;
196 
197 	/* check length of new label is ok */
198 	if (strlen(newVal) > (NAMEDATALEN - 1))
199 		ereport(ERROR,
200 				(errcode(ERRCODE_INVALID_NAME),
201 				 errmsg("invalid enum label \"%s\"", newVal),
202 				 errdetail("Labels must be %d characters or less.",
203 						   NAMEDATALEN - 1)));
204 
205 	/*
206 	 * Acquire a lock on the enum type, which we won't release until commit.
207 	 * This ensures that two backends aren't concurrently modifying the same
208 	 * enum type.  Without that, we couldn't be sure to get a consistent view
209 	 * of the enum members via the syscache.  Note that this does not block
210 	 * other backends from inspecting the type; see comments for
211 	 * RenumberEnumType.
212 	 */
213 	LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
214 
215 	/*
216 	 * Check if label is already in use.  The unique index on pg_enum would
217 	 * catch this anyway, but we prefer a friendlier error message, and
218 	 * besides we need a check to support IF NOT EXISTS.
219 	 */
220 	enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
221 							   ObjectIdGetDatum(enumTypeOid),
222 							   CStringGetDatum(newVal));
223 	if (HeapTupleIsValid(enum_tup))
224 	{
225 		ReleaseSysCache(enum_tup);
226 		if (skipIfExists)
227 		{
228 			ereport(NOTICE,
229 					(errcode(ERRCODE_DUPLICATE_OBJECT),
230 					 errmsg("enum label \"%s\" already exists, skipping",
231 							newVal)));
232 			return;
233 		}
234 		else
235 			ereport(ERROR,
236 					(errcode(ERRCODE_DUPLICATE_OBJECT),
237 					 errmsg("enum label \"%s\" already exists",
238 							newVal)));
239 	}
240 
241 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
242 
243 	/* If we have to renumber the existing members, we restart from here */
244 restart:
245 
246 	/* Get the list of existing members of the enum */
247 	list = SearchSysCacheList1(ENUMTYPOIDNAME,
248 							   ObjectIdGetDatum(enumTypeOid));
249 	nelems = list->n_members;
250 
251 	/* Sort the existing members by enumsortorder */
252 	existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
253 	for (i = 0; i < nelems; i++)
254 		existing[i] = &(list->members[i]->tuple);
255 
256 	qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
257 
258 	if (neighbor == NULL)
259 	{
260 		/*
261 		 * Put the new label at the end of the list. No change to existing
262 		 * tuples is required.
263 		 */
264 		if (nelems > 0)
265 		{
266 			Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
267 
268 			newelemorder = en->enumsortorder + 1;
269 		}
270 		else
271 			newelemorder = 1;
272 	}
273 	else
274 	{
275 		/* BEFORE or AFTER was specified */
276 		int			nbr_index;
277 		int			other_nbr_index;
278 		Form_pg_enum nbr_en;
279 		Form_pg_enum other_nbr_en;
280 
281 		/* Locate the neighbor element */
282 		for (nbr_index = 0; nbr_index < nelems; nbr_index++)
283 		{
284 			Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
285 
286 			if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
287 				break;
288 		}
289 		if (nbr_index >= nelems)
290 			ereport(ERROR,
291 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
292 					 errmsg("\"%s\" is not an existing enum label",
293 							neighbor)));
294 		nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
295 
296 		/*
297 		 * Attempt to assign an appropriate enumsortorder value: one less than
298 		 * the smallest member, one more than the largest member, or halfway
299 		 * between two existing members.
300 		 *
301 		 * In the "halfway" case, because of the finite precision of float4,
302 		 * we might compute a value that's actually equal to one or the other
303 		 * of its neighbors.  In that case we renumber the existing members
304 		 * and try again.
305 		 */
306 		if (newValIsAfter)
307 			other_nbr_index = nbr_index + 1;
308 		else
309 			other_nbr_index = nbr_index - 1;
310 
311 		if (other_nbr_index < 0)
312 			newelemorder = nbr_en->enumsortorder - 1;
313 		else if (other_nbr_index >= nelems)
314 			newelemorder = nbr_en->enumsortorder + 1;
315 		else
316 		{
317 			/*
318 			 * The midpoint value computed here has to be rounded to float4
319 			 * precision, else our equality comparisons against the adjacent
320 			 * values are meaningless.  The most portable way of forcing that
321 			 * to happen with non-C-standard-compliant compilers is to store
322 			 * it into a volatile variable.
323 			 */
324 			volatile float4 midpoint;
325 
326 			other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
327 			midpoint = (nbr_en->enumsortorder +
328 						other_nbr_en->enumsortorder) / 2;
329 
330 			if (midpoint == nbr_en->enumsortorder ||
331 				midpoint == other_nbr_en->enumsortorder)
332 			{
333 				RenumberEnumType(pg_enum, existing, nelems);
334 				/* Clean up and start over */
335 				pfree(existing);
336 				ReleaseCatCacheList(list);
337 				goto restart;
338 			}
339 
340 			newelemorder = midpoint;
341 		}
342 	}
343 
344 	/* Get a new OID for the new label */
345 	if (IsBinaryUpgrade)
346 	{
347 		if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
348 			ereport(ERROR,
349 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
350 					 errmsg("pg_enum OID value not set when in binary upgrade mode")));
351 
352 		/*
353 		 * Use binary-upgrade override for pg_enum.oid, if supplied. During
354 		 * binary upgrade, all pg_enum.oid's are set this way so they are
355 		 * guaranteed to be consistent.
356 		 */
357 		if (neighbor != NULL)
358 			ereport(ERROR,
359 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
360 					 errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
361 
362 		newOid = binary_upgrade_next_pg_enum_oid;
363 		binary_upgrade_next_pg_enum_oid = InvalidOid;
364 	}
365 	else
366 	{
367 		/*
368 		 * Normal case: we need to allocate a new Oid for the value.
369 		 *
370 		 * We want to give the new element an even-numbered Oid if it's safe,
371 		 * which is to say it compares correctly to all pre-existing even
372 		 * numbered Oids in the enum.  Otherwise, we must give it an odd Oid.
373 		 */
374 		for (;;)
375 		{
376 			bool		sorts_ok;
377 
378 			/* Get a new OID (different from all existing pg_enum tuples) */
379 			newOid = GetNewOid(pg_enum);
380 
381 			/*
382 			 * Detect whether it sorts correctly relative to existing
383 			 * even-numbered labels of the enum.  We can ignore existing
384 			 * labels with odd Oids, since a comparison involving one of those
385 			 * will not take the fast path anyway.
386 			 */
387 			sorts_ok = true;
388 			for (i = 0; i < nelems; i++)
389 			{
390 				HeapTuple	exists_tup = existing[i];
391 				Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
392 				Oid			exists_oid = HeapTupleGetOid(exists_tup);
393 
394 				if (exists_oid & 1)
395 					continue;	/* ignore odd Oids */
396 
397 				if (exists_en->enumsortorder < newelemorder)
398 				{
399 					/* should sort before */
400 					if (exists_oid >= newOid)
401 					{
402 						sorts_ok = false;
403 						break;
404 					}
405 				}
406 				else
407 				{
408 					/* should sort after */
409 					if (exists_oid <= newOid)
410 					{
411 						sorts_ok = false;
412 						break;
413 					}
414 				}
415 			}
416 
417 			if (sorts_ok)
418 			{
419 				/* If it's even and sorts OK, we're done. */
420 				if ((newOid & 1) == 0)
421 					break;
422 
423 				/*
424 				 * If it's odd, and sorts OK, loop back to get another OID and
425 				 * try again.  Probably, the next available even OID will sort
426 				 * correctly too, so it's worth trying.
427 				 */
428 			}
429 			else
430 			{
431 				/*
432 				 * If it's odd, and does not sort correctly, we're done.
433 				 * (Probably, the next available even OID would sort
434 				 * incorrectly too, so no point in trying again.)
435 				 */
436 				if (newOid & 1)
437 					break;
438 
439 				/*
440 				 * If it's even, and does not sort correctly, loop back to get
441 				 * another OID and try again.  (We *must* reject this case.)
442 				 */
443 			}
444 		}
445 	}
446 
447 	/* Done with info about existing members */
448 	pfree(existing);
449 	ReleaseCatCacheList(list);
450 
451 	/* Create the new pg_enum entry */
452 	memset(nulls, false, sizeof(nulls));
453 	values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
454 	values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
455 	namestrcpy(&enumlabel, newVal);
456 	values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
457 	enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
458 	HeapTupleSetOid(enum_tup, newOid);
459 	CatalogTupleInsert(pg_enum, enum_tup);
460 	heap_freetuple(enum_tup);
461 
462 	heap_close(pg_enum, RowExclusiveLock);
463 }
464 
465 
466 /*
467  * RenameEnumLabel
468  *		Rename a label in an enum set.
469  */
470 void
RenameEnumLabel(Oid enumTypeOid,const char * oldVal,const char * newVal)471 RenameEnumLabel(Oid enumTypeOid,
472 				const char *oldVal,
473 				const char *newVal)
474 {
475 	Relation	pg_enum;
476 	HeapTuple	enum_tup;
477 	Form_pg_enum en;
478 	CatCList   *list;
479 	int			nelems;
480 	HeapTuple	old_tup;
481 	bool		found_new;
482 	int			i;
483 
484 	/* check length of new label is ok */
485 	if (strlen(newVal) > (NAMEDATALEN - 1))
486 		ereport(ERROR,
487 				(errcode(ERRCODE_INVALID_NAME),
488 				 errmsg("invalid enum label \"%s\"", newVal),
489 				 errdetail("Labels must be %d characters or less.",
490 						   NAMEDATALEN - 1)));
491 
492 	/*
493 	 * Acquire a lock on the enum type, which we won't release until commit.
494 	 * This ensures that two backends aren't concurrently modifying the same
495 	 * enum type.  Since we are not changing the type's sort order, this is
496 	 * probably not really necessary, but there seems no reason not to take
497 	 * the lock to be sure.
498 	 */
499 	LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
500 
501 	pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
502 
503 	/* Get the list of existing members of the enum */
504 	list = SearchSysCacheList1(ENUMTYPOIDNAME,
505 							   ObjectIdGetDatum(enumTypeOid));
506 	nelems = list->n_members;
507 
508 	/*
509 	 * Locate the element to rename and check if the new label is already in
510 	 * use.  (The unique index on pg_enum would catch that anyway, but we
511 	 * prefer a friendlier error message.)
512 	 */
513 	old_tup = NULL;
514 	found_new = false;
515 	for (i = 0; i < nelems; i++)
516 	{
517 		enum_tup = &(list->members[i]->tuple);
518 		en = (Form_pg_enum) GETSTRUCT(enum_tup);
519 		if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
520 			old_tup = enum_tup;
521 		if (strcmp(NameStr(en->enumlabel), newVal) == 0)
522 			found_new = true;
523 	}
524 	if (!old_tup)
525 		ereport(ERROR,
526 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
527 				 errmsg("\"%s\" is not an existing enum label",
528 						oldVal)));
529 	if (found_new)
530 		ereport(ERROR,
531 				(errcode(ERRCODE_DUPLICATE_OBJECT),
532 				 errmsg("enum label \"%s\" already exists",
533 						newVal)));
534 
535 	/* OK, make a writable copy of old tuple */
536 	enum_tup = heap_copytuple(old_tup);
537 	en = (Form_pg_enum) GETSTRUCT(enum_tup);
538 
539 	ReleaseCatCacheList(list);
540 
541 	/* Update the pg_enum entry */
542 	namestrcpy(&en->enumlabel, newVal);
543 	CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
544 	heap_freetuple(enum_tup);
545 
546 	heap_close(pg_enum, RowExclusiveLock);
547 }
548 
549 
550 /*
551  * RenumberEnumType
552  *		Renumber existing enum elements to have sort positions 1..n.
553  *
554  * We avoid doing this unless absolutely necessary; in most installations
555  * it will never happen.  The reason is that updating existing pg_enum
556  * entries creates hazards for other backends that are concurrently reading
557  * pg_enum.  Although system catalog scans now use MVCC semantics, the
558  * syscache machinery might read different pg_enum entries under different
559  * snapshots, so some other backend might get confused about the proper
560  * ordering if a concurrent renumbering occurs.
561  *
562  * We therefore make the following choices:
563  *
564  * 1. Any code that is interested in the enumsortorder values MUST read
565  * all the relevant pg_enum entries with a single MVCC snapshot, or else
566  * acquire lock on the enum type to prevent concurrent execution of
567  * AddEnumLabel().
568  *
569  * 2. Code that is not examining enumsortorder can use a syscache
570  * (for example, enum_in and enum_out do so).
571  */
572 static void
RenumberEnumType(Relation pg_enum,HeapTuple * existing,int nelems)573 RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
574 {
575 	int			i;
576 
577 	/*
578 	 * We should only need to increase existing elements' enumsortorders,
579 	 * never decrease them.  Therefore, work from the end backwards, to avoid
580 	 * unwanted uniqueness violations.
581 	 */
582 	for (i = nelems - 1; i >= 0; i--)
583 	{
584 		HeapTuple	newtup;
585 		Form_pg_enum en;
586 		float4		newsortorder;
587 
588 		newtup = heap_copytuple(existing[i]);
589 		en = (Form_pg_enum) GETSTRUCT(newtup);
590 
591 		newsortorder = i + 1;
592 		if (en->enumsortorder != newsortorder)
593 		{
594 			en->enumsortorder = newsortorder;
595 
596 			CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
597 		}
598 
599 		heap_freetuple(newtup);
600 	}
601 
602 	/* Make the updates visible */
603 	CommandCounterIncrement();
604 }
605 
606 
607 /* qsort comparison function for tuples by sort order */
608 static int
sort_order_cmp(const void * p1,const void * p2)609 sort_order_cmp(const void *p1, const void *p2)
610 {
611 	HeapTuple	v1 = *((const HeapTuple *) p1);
612 	HeapTuple	v2 = *((const HeapTuple *) p2);
613 	Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
614 	Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
615 
616 	if (en1->enumsortorder < en2->enumsortorder)
617 		return -1;
618 	else if (en1->enumsortorder > en2->enumsortorder)
619 		return 1;
620 	else
621 		return 0;
622 }
623