Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * pg_enum.c
4 : * routines to support manipulation of the pg_enum relation
5 : *
6 : * Copyright (c) 2006-2017, 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
49 15 : 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 15 : 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 15 : 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 15 : oids = (Oid *) palloc(num_elems * sizeof(Oid));
80 :
81 59 : 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 82 : new_oid = GetNewOid(pg_enum);
93 82 : } while (new_oid & 1);
94 44 : oids[elemno] = new_oid;
95 : }
96 :
97 : /* sort them, just in case OID counter wrapped from high to low */
98 15 : qsort(oids, num_elems, sizeof(Oid), oid_cmp);
99 :
100 : /* and make the entries */
101 15 : memset(nulls, false, sizeof(nulls));
102 :
103 15 : elemno = 0;
104 59 : foreach(lc, vals)
105 : {
106 44 : 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 44 : if (strlen(lab) > (NAMEDATALEN - 1))
113 0 : 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 44 : values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
120 44 : values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
121 44 : namestrcpy(&enumlabel, lab);
122 44 : values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
123 :
124 44 : tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
125 44 : HeapTupleSetOid(tup, oids[elemno]);
126 :
127 44 : CatalogTupleInsert(pg_enum, tup);
128 44 : heap_freetuple(tup);
129 :
130 44 : elemno++;
131 : }
132 :
133 : /* clean up */
134 15 : pfree(oids);
135 15 : heap_close(pg_enum, RowExclusiveLock);
136 15 : }
137 :
138 :
139 : /*
140 : * EnumValuesDelete
141 : * Remove all the pg_enum entries for the specified enum type.
142 : */
143 : void
144 8 : EnumValuesDelete(Oid enumTypeOid)
145 : {
146 : Relation pg_enum;
147 : ScanKeyData key[1];
148 : SysScanDesc scan;
149 : HeapTuple tup;
150 :
151 8 : pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
152 :
153 8 : ScanKeyInit(&key[0],
154 : Anum_pg_enum_enumtypid,
155 : BTEqualStrategyNumber, F_OIDEQ,
156 : ObjectIdGetDatum(enumTypeOid));
157 :
158 8 : scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
159 : NULL, 1, key);
160 :
161 43 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
162 : {
163 27 : CatalogTupleDelete(pg_enum, &tup->t_self);
164 : }
165 :
166 8 : systable_endscan(scan);
167 :
168 8 : heap_close(pg_enum, RowExclusiveLock);
169 8 : }
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
179 44 : 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 44 : if (strlen(newVal) > (NAMEDATALEN - 1))
199 1 : 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 43 : 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 43 : enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
221 : ObjectIdGetDatum(enumTypeOid),
222 : CStringGetDatum(newVal));
223 43 : if (HeapTupleIsValid(enum_tup))
224 : {
225 2 : ReleaseSysCache(enum_tup);
226 2 : if (skipIfExists)
227 : {
228 1 : ereport(NOTICE,
229 : (errcode(ERRCODE_DUPLICATE_OBJECT),
230 : errmsg("enum label \"%s\" already exists, skipping",
231 : newVal)));
232 42 : return;
233 : }
234 : else
235 1 : ereport(ERROR,
236 : (errcode(ERRCODE_DUPLICATE_OBJECT),
237 : errmsg("enum label \"%s\" already exists",
238 : newVal)));
239 : }
240 :
241 41 : 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 42 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
248 : ObjectIdGetDatum(enumTypeOid));
249 42 : nelems = list->n_members;
250 :
251 : /* Sort the existing members by enumsortorder */
252 42 : existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
253 609 : for (i = 0; i < nelems; i++)
254 567 : existing[i] = &(list->members[i]->tuple);
255 :
256 42 : qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
257 :
258 42 : 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 6 : if (nelems > 0)
265 : {
266 6 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
267 :
268 6 : newelemorder = en->enumsortorder + 1;
269 : }
270 : else
271 0 : 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 546 : for (nbr_index = 0; nbr_index < nelems; nbr_index++)
283 : {
284 545 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
285 :
286 545 : if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
287 35 : break;
288 : }
289 36 : if (nbr_index >= nelems)
290 1 : ereport(ERROR,
291 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
292 : errmsg("\"%s\" is not an existing enum label",
293 : neighbor)));
294 35 : 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 35 : if (newValIsAfter)
307 2 : other_nbr_index = nbr_index + 1;
308 : else
309 33 : other_nbr_index = nbr_index - 1;
310 :
311 35 : if (other_nbr_index < 0)
312 1 : newelemorder = nbr_en->enumsortorder - 1;
313 34 : else if (other_nbr_index >= nelems)
314 1 : 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 33 : other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
327 66 : midpoint = (nbr_en->enumsortorder +
328 33 : other_nbr_en->enumsortorder) / 2;
329 :
330 65 : if (midpoint == nbr_en->enumsortorder ||
331 32 : midpoint == other_nbr_en->enumsortorder)
332 : {
333 1 : RenumberEnumType(pg_enum, existing, nelems);
334 : /* Clean up and start over */
335 1 : pfree(existing);
336 1 : ReleaseCatCacheList(list);
337 1 : goto restart;
338 : }
339 :
340 32 : newelemorder = midpoint;
341 : }
342 : }
343 :
344 : /* Get a new OID for the new label */
345 40 : if (IsBinaryUpgrade)
346 : {
347 0 : if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
348 0 : 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 0 : if (neighbor != NULL)
358 0 : ereport(ERROR,
359 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
360 : errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
361 :
362 0 : newOid = binary_upgrade_next_pg_enum_oid;
363 0 : 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 76 : 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 76 : sorts_ok = true;
388 1063 : for (i = 0; i < nelems; i++)
389 : {
390 1051 : HeapTuple exists_tup = existing[i];
391 1051 : Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
392 1051 : Oid exists_oid = HeapTupleGetOid(exists_tup);
393 :
394 1051 : if (exists_oid & 1)
395 885 : continue; /* ignore odd Oids */
396 :
397 166 : if (exists_en->enumsortorder < newelemorder)
398 : {
399 : /* should sort before */
400 102 : if (exists_oid >= newOid)
401 : {
402 0 : sorts_ok = false;
403 0 : break;
404 : }
405 : }
406 : else
407 : {
408 : /* should sort after */
409 64 : if (exists_oid <= newOid)
410 : {
411 64 : sorts_ok = false;
412 64 : break;
413 : }
414 : }
415 : }
416 :
417 76 : if (sorts_ok)
418 : {
419 : /* If it's even and sorts OK, we're done. */
420 12 : if ((newOid & 1) == 0)
421 7 : 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 64 : if (newOid & 1)
437 33 : 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 36 : }
445 : }
446 :
447 : /* Done with info about existing members */
448 40 : pfree(existing);
449 40 : ReleaseCatCacheList(list);
450 :
451 : /* Create the new pg_enum entry */
452 40 : memset(nulls, false, sizeof(nulls));
453 40 : values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
454 40 : values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
455 40 : namestrcpy(&enumlabel, newVal);
456 40 : values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
457 40 : enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
458 40 : HeapTupleSetOid(enum_tup, newOid);
459 40 : CatalogTupleInsert(pg_enum, enum_tup);
460 40 : heap_freetuple(enum_tup);
461 :
462 40 : heap_close(pg_enum, RowExclusiveLock);
463 : }
464 :
465 :
466 : /*
467 : * RenameEnumLabel
468 : * Rename a label in an enum set.
469 : */
470 : void
471 3 : 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 3 : if (strlen(newVal) > (NAMEDATALEN - 1))
486 0 : 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 3 : LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
500 :
501 3 : pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
502 :
503 : /* Get the list of existing members of the enum */
504 3 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
505 : ObjectIdGetDatum(enumTypeOid));
506 3 : 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 3 : old_tup = NULL;
514 3 : found_new = false;
515 21 : for (i = 0; i < nelems; i++)
516 : {
517 18 : enum_tup = &(list->members[i]->tuple);
518 18 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
519 18 : if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
520 2 : old_tup = enum_tup;
521 18 : if (strcmp(NameStr(en->enumlabel), newVal) == 0)
522 2 : found_new = true;
523 : }
524 3 : if (!old_tup)
525 1 : ereport(ERROR,
526 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
527 : errmsg("\"%s\" is not an existing enum label",
528 : oldVal)));
529 2 : if (found_new)
530 1 : 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 1 : enum_tup = heap_copytuple(old_tup);
537 1 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
538 :
539 1 : ReleaseCatCacheList(list);
540 :
541 : /* Update the pg_enum entry */
542 1 : namestrcpy(&en->enumlabel, newVal);
543 1 : CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
544 1 : heap_freetuple(enum_tup);
545 :
546 1 : heap_close(pg_enum, RowExclusiveLock);
547 1 : }
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
573 1 : 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 26 : for (i = nelems - 1; i >= 0; i--)
583 : {
584 : HeapTuple newtup;
585 : Form_pg_enum en;
586 : float4 newsortorder;
587 :
588 25 : newtup = heap_copytuple(existing[i]);
589 25 : en = (Form_pg_enum) GETSTRUCT(newtup);
590 :
591 25 : newsortorder = i + 1;
592 25 : if (en->enumsortorder != newsortorder)
593 : {
594 24 : en->enumsortorder = newsortorder;
595 :
596 24 : CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
597 : }
598 :
599 25 : heap_freetuple(newtup);
600 : }
601 :
602 : /* Make the updates visible */
603 1 : CommandCounterIncrement();
604 1 : }
605 :
606 :
607 : /* qsort comparison function for tuples by sort order */
608 : static int
609 2240 : sort_order_cmp(const void *p1, const void *p2)
610 : {
611 2240 : HeapTuple v1 = *((const HeapTuple *) p1);
612 2240 : HeapTuple v2 = *((const HeapTuple *) p2);
613 2240 : Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
614 2240 : Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
615 :
616 2240 : if (en1->enumsortorder < en2->enumsortorder)
617 937 : return -1;
618 1303 : else if (en1->enumsortorder > en2->enumsortorder)
619 1303 : return 1;
620 : else
621 0 : return 0;
622 : }
|