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