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-2024, 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 : * We keep two transaction-lifespan hash tables, one containing the OIDs
40 : * of enum types made in the current transaction, and one containing the
41 : * OIDs of enum values created during the current transaction by
42 : * AddEnumLabel (but only if their enum type is not in the first hash).
43 : *
44 : * We disallow using enum values in the second hash until the transaction is
45 : * committed; otherwise, they might get into indexes where we can't clean
46 : * them up, and then if the transaction rolls back we have a broken index.
47 : * (See comments for check_safe_enum_use() in enum.c.) Values created by
48 : * EnumValuesCreate are *not* entered into the table; we assume those are
49 : * created during CREATE TYPE, so they can't go away unless the enum type
50 : * itself does.
51 : *
52 : * The motivation for treating enum values as safe if their type OID is
53 : * in the first hash is to allow CREATE TYPE AS ENUM; ALTER TYPE ADD VALUE;
54 : * followed by a use of the value in the same transaction. This pattern
55 : * is really just as safe as creating the value during CREATE TYPE.
56 : * We need to support this because pg_dump in binary upgrade mode produces
57 : * commands like that. But currently we only support it when the commands
58 : * are at the outermost transaction level, which is as much as we need for
59 : * pg_dump. We could track subtransaction nesting of the commands to
60 : * analyze things more precisely, but for now we don't bother.
61 : */
62 : static HTAB *uncommitted_enum_types = NULL;
63 : static HTAB *uncommitted_enum_values = NULL;
64 :
65 : static void init_uncommitted_enum_types(void);
66 : static void init_uncommitted_enum_values(void);
67 : static bool EnumTypeUncommitted(Oid typ_id);
68 : static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
69 : static int sort_order_cmp(const void *p1, const void *p2);
70 :
71 :
72 : /*
73 : * EnumValuesCreate
74 : * Create an entry in pg_enum for each of the supplied enum values.
75 : *
76 : * vals is a list of String values.
77 : *
78 : * We assume that this is called only by CREATE TYPE AS ENUM, and that it
79 : * will be called even if the vals list is empty. So we can enter the
80 : * enum type's OID into uncommitted_enum_types here, rather than needing
81 : * another entry point to do it.
82 : */
83 : void
84 428 : EnumValuesCreate(Oid enumTypeOid, List *vals)
85 : {
86 : Relation pg_enum;
87 : Oid *oids;
88 : int elemno,
89 : num_elems;
90 : ListCell *lc;
91 428 : int slotCount = 0;
92 : int nslots;
93 : CatalogIndexState indstate;
94 : TupleTableSlot **slot;
95 :
96 : /*
97 : * Remember the type OID as being made in the current transaction, but not
98 : * if we're in a subtransaction. (We could remember the OID anyway, in
99 : * case a subsequent ALTER ADD VALUE occurs at outer level. But that
100 : * usage pattern seems unlikely enough that we'd probably just be wasting
101 : * hashtable maintenance effort.)
102 : */
103 428 : if (GetCurrentTransactionNestLevel() == 1)
104 : {
105 428 : if (uncommitted_enum_types == NULL)
106 422 : init_uncommitted_enum_types();
107 428 : (void) hash_search(uncommitted_enum_types, &enumTypeOid,
108 : HASH_ENTER, NULL);
109 : }
110 :
111 428 : num_elems = list_length(vals);
112 :
113 : /*
114 : * We do not bother to check the list of values for duplicates --- if you
115 : * have any, you'll get a less-than-friendly unique-index violation. It is
116 : * probably not worth trying harder.
117 : */
118 :
119 428 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
120 :
121 : /*
122 : * Allocate OIDs for the enum's members.
123 : *
124 : * While this method does not absolutely guarantee that we generate no
125 : * duplicate OIDs (since we haven't entered each oid into the table before
126 : * allocating the next), trouble could only occur if the OID counter wraps
127 : * all the way around before we finish. Which seems unlikely.
128 : */
129 428 : oids = (Oid *) palloc(num_elems * sizeof(Oid));
130 :
131 250996 : for (elemno = 0; elemno < num_elems; elemno++)
132 : {
133 : /*
134 : * We assign even-numbered OIDs to all the new enum labels. This
135 : * tells the comparison functions the OIDs are in the correct sort
136 : * order and can be compared directly.
137 : */
138 : Oid new_oid;
139 :
140 : do
141 : {
142 501072 : new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
143 : Anum_pg_enum_oid);
144 501072 : } while (new_oid & 1);
145 250568 : oids[elemno] = new_oid;
146 : }
147 :
148 : /* sort them, just in case OID counter wrapped from high to low */
149 428 : qsort(oids, num_elems, sizeof(Oid), oid_cmp);
150 :
151 : /* and make the entries */
152 428 : indstate = CatalogOpenIndexes(pg_enum);
153 :
154 : /* allocate the slots to use and initialize them */
155 428 : nslots = Min(num_elems,
156 : MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_enum));
157 428 : slot = palloc(sizeof(TupleTableSlot *) * nslots);
158 216496 : for (int i = 0; i < nslots; i++)
159 216068 : slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_enum),
160 : &TTSOpsHeapTuple);
161 :
162 428 : elemno = 0;
163 250996 : foreach(lc, vals)
164 : {
165 250568 : char *lab = strVal(lfirst(lc));
166 250568 : Name enumlabel = palloc0(NAMEDATALEN);
167 :
168 : /*
169 : * labels are stored in a name field, for easier syscache lookup, so
170 : * check the length to make sure it's within range.
171 : */
172 250568 : if (strlen(lab) > (NAMEDATALEN - 1))
173 0 : ereport(ERROR,
174 : (errcode(ERRCODE_INVALID_NAME),
175 : errmsg("invalid enum label \"%s\"", lab),
176 : errdetail("Labels must be %d bytes or less.",
177 : NAMEDATALEN - 1)));
178 :
179 250568 : ExecClearTuple(slot[slotCount]);
180 :
181 250568 : memset(slot[slotCount]->tts_isnull, false,
182 250568 : slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
183 :
184 250568 : slot[slotCount]->tts_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
185 250568 : slot[slotCount]->tts_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
186 250568 : slot[slotCount]->tts_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
187 :
188 250568 : namestrcpy(enumlabel, lab);
189 250568 : slot[slotCount]->tts_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(enumlabel);
190 :
191 250568 : ExecStoreVirtualTuple(slot[slotCount]);
192 250568 : slotCount++;
193 :
194 : /* if slots are full, insert a batch of tuples */
195 250568 : if (slotCount == nslots)
196 : {
197 420 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
198 : indstate);
199 420 : slotCount = 0;
200 : }
201 :
202 250568 : elemno++;
203 : }
204 :
205 : /* Insert any tuples left in the buffer */
206 428 : if (slotCount > 0)
207 250 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
208 : indstate);
209 :
210 : /* clean up */
211 428 : pfree(oids);
212 216496 : for (int i = 0; i < nslots; i++)
213 216068 : ExecDropSingleTupleTableSlot(slot[i]);
214 428 : CatalogCloseIndexes(indstate);
215 428 : table_close(pg_enum, RowExclusiveLock);
216 428 : }
217 :
218 :
219 : /*
220 : * EnumValuesDelete
221 : * Remove all the pg_enum entries for the specified enum type.
222 : */
223 : void
224 318 : EnumValuesDelete(Oid enumTypeOid)
225 : {
226 : Relation pg_enum;
227 : ScanKeyData key[1];
228 : SysScanDesc scan;
229 : HeapTuple tup;
230 :
231 318 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
232 :
233 318 : ScanKeyInit(&key[0],
234 : Anum_pg_enum_enumtypid,
235 : BTEqualStrategyNumber, F_OIDEQ,
236 : ObjectIdGetDatum(enumTypeOid));
237 :
238 318 : scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
239 : NULL, 1, key);
240 :
241 250528 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
242 : {
243 250210 : CatalogTupleDelete(pg_enum, &tup->t_self);
244 : }
245 :
246 318 : systable_endscan(scan);
247 :
248 318 : table_close(pg_enum, RowExclusiveLock);
249 318 : }
250 :
251 : /*
252 : * Initialize the uncommitted enum types table for this transaction.
253 : */
254 : static void
255 422 : init_uncommitted_enum_types(void)
256 : {
257 : HASHCTL hash_ctl;
258 :
259 422 : hash_ctl.keysize = sizeof(Oid);
260 422 : hash_ctl.entrysize = sizeof(Oid);
261 422 : hash_ctl.hcxt = TopTransactionContext;
262 422 : uncommitted_enum_types = hash_create("Uncommitted enum types",
263 : 32,
264 : &hash_ctl,
265 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
266 422 : }
267 :
268 : /*
269 : * Initialize the uncommitted enum values table for this transaction.
270 : */
271 : static void
272 234 : init_uncommitted_enum_values(void)
273 : {
274 : HASHCTL hash_ctl;
275 :
276 234 : hash_ctl.keysize = sizeof(Oid);
277 234 : hash_ctl.entrysize = sizeof(Oid);
278 234 : hash_ctl.hcxt = TopTransactionContext;
279 234 : uncommitted_enum_values = hash_create("Uncommitted enum values",
280 : 32,
281 : &hash_ctl,
282 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
283 234 : }
284 :
285 : /*
286 : * AddEnumLabel
287 : * Add a new label to the enum set. By default it goes at
288 : * the end, but the user can choose to place it before or
289 : * after any existing set member.
290 : */
291 : void
292 370 : AddEnumLabel(Oid enumTypeOid,
293 : const char *newVal,
294 : const char *neighbor,
295 : bool newValIsAfter,
296 : bool skipIfExists)
297 : {
298 : Relation pg_enum;
299 : Oid newOid;
300 : Datum values[Natts_pg_enum];
301 : bool nulls[Natts_pg_enum];
302 : NameData enumlabel;
303 : HeapTuple enum_tup;
304 : float4 newelemorder;
305 : HeapTuple *existing;
306 : CatCList *list;
307 : int nelems;
308 : int i;
309 :
310 : /* check length of new label is ok */
311 370 : if (strlen(newVal) > (NAMEDATALEN - 1))
312 6 : ereport(ERROR,
313 : (errcode(ERRCODE_INVALID_NAME),
314 : errmsg("invalid enum label \"%s\"", newVal),
315 : errdetail("Labels must be %d bytes or less.",
316 : NAMEDATALEN - 1)));
317 :
318 : /*
319 : * Acquire a lock on the enum type, which we won't release until commit.
320 : * This ensures that two backends aren't concurrently modifying the same
321 : * enum type. Without that, we couldn't be sure to get a consistent view
322 : * of the enum members via the syscache. Note that this does not block
323 : * other backends from inspecting the type; see comments for
324 : * RenumberEnumType.
325 : */
326 364 : LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
327 :
328 : /*
329 : * Check if label is already in use. The unique index on pg_enum would
330 : * catch this anyway, but we prefer a friendlier error message, and
331 : * besides we need a check to support IF NOT EXISTS.
332 : */
333 364 : enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
334 : ObjectIdGetDatum(enumTypeOid),
335 : CStringGetDatum(newVal));
336 364 : if (HeapTupleIsValid(enum_tup))
337 : {
338 12 : ReleaseSysCache(enum_tup);
339 12 : if (skipIfExists)
340 : {
341 6 : ereport(NOTICE,
342 : (errcode(ERRCODE_DUPLICATE_OBJECT),
343 : errmsg("enum label \"%s\" already exists, skipping",
344 : newVal)));
345 118 : return;
346 : }
347 : else
348 6 : ereport(ERROR,
349 : (errcode(ERRCODE_DUPLICATE_OBJECT),
350 : errmsg("enum label \"%s\" already exists",
351 : newVal)));
352 : }
353 :
354 352 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
355 :
356 : /* If we have to renumber the existing members, we restart from here */
357 358 : restart:
358 :
359 : /* Get the list of existing members of the enum */
360 358 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
361 : ObjectIdGetDatum(enumTypeOid));
362 358 : nelems = list->n_members;
363 :
364 : /* Sort the existing members by enumsortorder */
365 358 : existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
366 4880 : for (i = 0; i < nelems; i++)
367 4522 : existing[i] = &(list->members[i]->tuple);
368 :
369 358 : qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
370 :
371 358 : if (neighbor == NULL)
372 : {
373 : /*
374 : * Put the new label at the end of the list. No change to existing
375 : * tuples is required.
376 : */
377 136 : if (nelems > 0)
378 : {
379 128 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
380 :
381 128 : newelemorder = en->enumsortorder + 1;
382 : }
383 : else
384 8 : newelemorder = 1;
385 : }
386 : else
387 : {
388 : /* BEFORE or AFTER was specified */
389 : int nbr_index;
390 : int other_nbr_index;
391 : Form_pg_enum nbr_en;
392 : Form_pg_enum other_nbr_en;
393 :
394 : /* Locate the neighbor element */
395 3286 : for (nbr_index = 0; nbr_index < nelems; nbr_index++)
396 : {
397 3280 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
398 :
399 3280 : if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
400 216 : break;
401 : }
402 222 : if (nbr_index >= nelems)
403 6 : ereport(ERROR,
404 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
405 : errmsg("\"%s\" is not an existing enum label",
406 : neighbor)));
407 216 : nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
408 :
409 : /*
410 : * Attempt to assign an appropriate enumsortorder value: one less than
411 : * the smallest member, one more than the largest member, or halfway
412 : * between two existing members.
413 : *
414 : * In the "halfway" case, because of the finite precision of float4,
415 : * we might compute a value that's actually equal to one or the other
416 : * of its neighbors. In that case we renumber the existing members
417 : * and try again.
418 : */
419 216 : if (newValIsAfter)
420 16 : other_nbr_index = nbr_index + 1;
421 : else
422 200 : other_nbr_index = nbr_index - 1;
423 :
424 216 : if (other_nbr_index < 0)
425 8 : newelemorder = nbr_en->enumsortorder - 1;
426 208 : else if (other_nbr_index >= nelems)
427 8 : newelemorder = nbr_en->enumsortorder + 1;
428 : else
429 : {
430 : /*
431 : * The midpoint value computed here has to be rounded to float4
432 : * precision, else our equality comparisons against the adjacent
433 : * values are meaningless. The most portable way of forcing that
434 : * to happen with non-C-standard-compliant compilers is to store
435 : * it into a volatile variable.
436 : */
437 : volatile float4 midpoint;
438 :
439 200 : other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
440 200 : midpoint = (nbr_en->enumsortorder +
441 200 : other_nbr_en->enumsortorder) / 2;
442 :
443 200 : if (midpoint == nbr_en->enumsortorder ||
444 194 : midpoint == other_nbr_en->enumsortorder)
445 : {
446 6 : RenumberEnumType(pg_enum, existing, nelems);
447 : /* Clean up and start over */
448 6 : pfree(existing);
449 6 : ReleaseCatCacheList(list);
450 6 : goto restart;
451 : }
452 :
453 194 : newelemorder = midpoint;
454 : }
455 : }
456 :
457 : /* Get a new OID for the new label */
458 346 : if (IsBinaryUpgrade)
459 : {
460 100 : if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
461 0 : ereport(ERROR,
462 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
463 : errmsg("pg_enum OID value not set when in binary upgrade mode")));
464 :
465 : /*
466 : * Use binary-upgrade override for pg_enum.oid, if supplied. During
467 : * binary upgrade, all pg_enum.oid's are set this way so they are
468 : * guaranteed to be consistent.
469 : */
470 100 : if (neighbor != NULL)
471 0 : ereport(ERROR,
472 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
473 : errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
474 :
475 100 : newOid = binary_upgrade_next_pg_enum_oid;
476 100 : binary_upgrade_next_pg_enum_oid = InvalidOid;
477 : }
478 : else
479 : {
480 : /*
481 : * Normal case: we need to allocate a new Oid for the value.
482 : *
483 : * We want to give the new element an even-numbered Oid if it's safe,
484 : * which is to say it compares correctly to all pre-existing even
485 : * numbered Oids in the enum. Otherwise, we must give it an odd Oid.
486 : */
487 : for (;;)
488 220 : {
489 : bool sorts_ok;
490 :
491 : /* Get a new OID (different from all existing pg_enum tuples) */
492 466 : newOid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
493 : Anum_pg_enum_oid);
494 :
495 : /*
496 : * Detect whether it sorts correctly relative to existing
497 : * even-numbered labels of the enum. We can ignore existing
498 : * labels with odd Oids, since a comparison involving one of those
499 : * will not take the fast path anyway.
500 : */
501 466 : sorts_ok = true;
502 6374 : for (i = 0; i < nelems; i++)
503 : {
504 6294 : HeapTuple exists_tup = existing[i];
505 6294 : Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
506 6294 : Oid exists_oid = exists_en->oid;
507 :
508 6294 : if (exists_oid & 1)
509 5284 : continue; /* ignore odd Oids */
510 :
511 1010 : if (exists_en->enumsortorder < newelemorder)
512 : {
513 : /* should sort before */
514 624 : if (exists_oid >= newOid)
515 : {
516 0 : sorts_ok = false;
517 0 : break;
518 : }
519 : }
520 : else
521 : {
522 : /* should sort after */
523 386 : if (exists_oid <= newOid)
524 : {
525 386 : sorts_ok = false;
526 386 : break;
527 : }
528 : }
529 : }
530 :
531 466 : if (sorts_ok)
532 : {
533 : /* If it's even and sorts OK, we're done. */
534 80 : if ((newOid & 1) == 0)
535 44 : break;
536 :
537 : /*
538 : * If it's odd, and sorts OK, loop back to get another OID and
539 : * try again. Probably, the next available even OID will sort
540 : * correctly too, so it's worth trying.
541 : */
542 : }
543 : else
544 : {
545 : /*
546 : * If it's odd, and does not sort correctly, we're done.
547 : * (Probably, the next available even OID would sort
548 : * incorrectly too, so no point in trying again.)
549 : */
550 386 : if (newOid & 1)
551 202 : break;
552 :
553 : /*
554 : * If it's even, and does not sort correctly, loop back to get
555 : * another OID and try again. (We *must* reject this case.)
556 : */
557 : }
558 : }
559 : }
560 :
561 : /* Done with info about existing members */
562 346 : pfree(existing);
563 346 : ReleaseCatCacheList(list);
564 :
565 : /* Create the new pg_enum entry */
566 346 : memset(nulls, false, sizeof(nulls));
567 346 : values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
568 346 : values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
569 346 : values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
570 346 : namestrcpy(&enumlabel, newVal);
571 346 : values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
572 346 : enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
573 346 : CatalogTupleInsert(pg_enum, enum_tup);
574 346 : heap_freetuple(enum_tup);
575 :
576 346 : table_close(pg_enum, RowExclusiveLock);
577 :
578 : /*
579 : * If the enum type itself is uncommitted, we need not enter the new enum
580 : * value into uncommitted_enum_values, because the type won't survive if
581 : * the value doesn't. (This is basically the same reasoning as for values
582 : * made directly by CREATE TYPE AS ENUM.) However, apply this rule only
583 : * when we are not inside a subtransaction; if we're more deeply nested
584 : * than the CREATE TYPE then the conclusion doesn't hold. We could expend
585 : * more effort to track the subtransaction level of CREATE TYPE, but for
586 : * now we're only concerned about making the world safe for pg_dump in
587 : * binary upgrade mode, and that won't use subtransactions.
588 : */
589 692 : if (GetCurrentTransactionNestLevel() == 1 &&
590 346 : EnumTypeUncommitted(enumTypeOid))
591 112 : return;
592 :
593 : /* Set up the uncommitted values table if not already done in this tx */
594 234 : if (uncommitted_enum_values == NULL)
595 234 : init_uncommitted_enum_values();
596 :
597 : /* Add the new value to the table */
598 234 : (void) hash_search(uncommitted_enum_values, &newOid, HASH_ENTER, NULL);
599 : }
600 :
601 :
602 : /*
603 : * RenameEnumLabel
604 : * Rename a label in an enum set.
605 : */
606 : void
607 24 : RenameEnumLabel(Oid enumTypeOid,
608 : const char *oldVal,
609 : const char *newVal)
610 : {
611 : Relation pg_enum;
612 : HeapTuple enum_tup;
613 : Form_pg_enum en;
614 : CatCList *list;
615 : int nelems;
616 : HeapTuple old_tup;
617 : bool found_new;
618 : int i;
619 :
620 : /* check length of new label is ok */
621 24 : if (strlen(newVal) > (NAMEDATALEN - 1))
622 0 : ereport(ERROR,
623 : (errcode(ERRCODE_INVALID_NAME),
624 : errmsg("invalid enum label \"%s\"", newVal),
625 : errdetail("Labels must be %d bytes or less.",
626 : NAMEDATALEN - 1)));
627 :
628 : /*
629 : * Acquire a lock on the enum type, which we won't release until commit.
630 : * This ensures that two backends aren't concurrently modifying the same
631 : * enum type. Since we are not changing the type's sort order, this is
632 : * probably not really necessary, but there seems no reason not to take
633 : * the lock to be sure.
634 : */
635 24 : LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
636 :
637 24 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
638 :
639 : /* Get the list of existing members of the enum */
640 24 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
641 : ObjectIdGetDatum(enumTypeOid));
642 24 : nelems = list->n_members;
643 :
644 : /*
645 : * Locate the element to rename and check if the new label is already in
646 : * use. (The unique index on pg_enum would catch that anyway, but we
647 : * prefer a friendlier error message.)
648 : */
649 24 : old_tup = NULL;
650 24 : found_new = false;
651 144 : for (i = 0; i < nelems; i++)
652 : {
653 120 : enum_tup = &(list->members[i]->tuple);
654 120 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
655 120 : if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
656 18 : old_tup = enum_tup;
657 120 : if (strcmp(NameStr(en->enumlabel), newVal) == 0)
658 12 : found_new = true;
659 : }
660 24 : if (!old_tup)
661 6 : ereport(ERROR,
662 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
663 : errmsg("\"%s\" is not an existing enum label",
664 : oldVal)));
665 18 : if (found_new)
666 6 : ereport(ERROR,
667 : (errcode(ERRCODE_DUPLICATE_OBJECT),
668 : errmsg("enum label \"%s\" already exists",
669 : newVal)));
670 :
671 : /* OK, make a writable copy of old tuple */
672 12 : enum_tup = heap_copytuple(old_tup);
673 12 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
674 :
675 12 : ReleaseCatCacheList(list);
676 :
677 : /* Update the pg_enum entry */
678 12 : namestrcpy(&en->enumlabel, newVal);
679 12 : CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
680 12 : heap_freetuple(enum_tup);
681 :
682 12 : table_close(pg_enum, RowExclusiveLock);
683 12 : }
684 :
685 :
686 : /*
687 : * Test if the given type OID is in the table of uncommitted enum types.
688 : */
689 : static bool
690 346 : EnumTypeUncommitted(Oid typ_id)
691 : {
692 : bool found;
693 :
694 : /* If we've made no uncommitted types table, it's not in the table */
695 346 : if (uncommitted_enum_types == NULL)
696 234 : return false;
697 :
698 : /* Else, is it in the table? */
699 112 : (void) hash_search(uncommitted_enum_types, &typ_id, HASH_FIND, &found);
700 112 : return found;
701 : }
702 :
703 :
704 : /*
705 : * Test if the given enum value is in the table of uncommitted enum values.
706 : */
707 : bool
708 90 : EnumUncommitted(Oid enum_id)
709 : {
710 : bool found;
711 :
712 : /* If we've made no uncommitted values table, it's not in the table */
713 90 : if (uncommitted_enum_values == NULL)
714 66 : return false;
715 :
716 : /* Else, is it in the table? */
717 24 : (void) hash_search(uncommitted_enum_values, &enum_id, HASH_FIND, &found);
718 24 : return found;
719 : }
720 :
721 :
722 : /*
723 : * Clean up enum stuff after end of top-level transaction.
724 : */
725 : void
726 746974 : AtEOXact_Enum(void)
727 : {
728 : /*
729 : * Reset the uncommitted tables, as all our tuples are now committed. The
730 : * memory will go away automatically when TopTransactionContext is freed;
731 : * it's sufficient to clear our pointers.
732 : */
733 746974 : uncommitted_enum_types = NULL;
734 746974 : uncommitted_enum_values = NULL;
735 746974 : }
736 :
737 :
738 : /*
739 : * RenumberEnumType
740 : * Renumber existing enum elements to have sort positions 1..n.
741 : *
742 : * We avoid doing this unless absolutely necessary; in most installations
743 : * it will never happen. The reason is that updating existing pg_enum
744 : * entries creates hazards for other backends that are concurrently reading
745 : * pg_enum. Although system catalog scans now use MVCC semantics, the
746 : * syscache machinery might read different pg_enum entries under different
747 : * snapshots, so some other backend might get confused about the proper
748 : * ordering if a concurrent renumbering occurs.
749 : *
750 : * We therefore make the following choices:
751 : *
752 : * 1. Any code that is interested in the enumsortorder values MUST read
753 : * all the relevant pg_enum entries with a single MVCC snapshot, or else
754 : * acquire lock on the enum type to prevent concurrent execution of
755 : * AddEnumLabel().
756 : *
757 : * 2. Code that is not examining enumsortorder can use a syscache
758 : * (for example, enum_in and enum_out do so).
759 : */
760 : static void
761 6 : RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
762 : {
763 : int i;
764 :
765 : /*
766 : * We should only need to increase existing elements' enumsortorders,
767 : * never decrease them. Therefore, work from the end backwards, to avoid
768 : * unwanted uniqueness violations.
769 : */
770 156 : for (i = nelems - 1; i >= 0; i--)
771 : {
772 : HeapTuple newtup;
773 : Form_pg_enum en;
774 : float4 newsortorder;
775 :
776 150 : newtup = heap_copytuple(existing[i]);
777 150 : en = (Form_pg_enum) GETSTRUCT(newtup);
778 :
779 150 : newsortorder = i + 1;
780 150 : if (en->enumsortorder != newsortorder)
781 : {
782 144 : en->enumsortorder = newsortorder;
783 :
784 144 : CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
785 : }
786 :
787 150 : heap_freetuple(newtup);
788 : }
789 :
790 : /* Make the updates visible */
791 6 : CommandCounterIncrement();
792 6 : }
793 :
794 :
795 : /* qsort comparison function for tuples by sort order */
796 : static int
797 17642 : sort_order_cmp(const void *p1, const void *p2)
798 : {
799 17642 : HeapTuple v1 = *((const HeapTuple *) p1);
800 17642 : HeapTuple v2 = *((const HeapTuple *) p2);
801 17642 : Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
802 17642 : Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
803 :
804 17642 : if (en1->enumsortorder < en2->enumsortorder)
805 7470 : return -1;
806 10172 : else if (en1->enumsortorder > en2->enumsortorder)
807 10172 : return 1;
808 : else
809 0 : return 0;
810 : }
811 :
812 : Size
813 886 : EstimateUncommittedEnumsSpace(void)
814 : {
815 886 : size_t entries = 0;
816 :
817 886 : if (uncommitted_enum_types)
818 0 : entries += hash_get_num_entries(uncommitted_enum_types);
819 886 : if (uncommitted_enum_values)
820 0 : entries += hash_get_num_entries(uncommitted_enum_values);
821 :
822 : /* Add two for the terminators. */
823 886 : return sizeof(Oid) * (entries + 2);
824 : }
825 :
826 : void
827 886 : SerializeUncommittedEnums(void *space, Size size)
828 : {
829 886 : Oid *serialized = (Oid *) space;
830 :
831 : /*
832 : * Make sure the hash tables haven't changed in size since the caller
833 : * reserved the space.
834 : */
835 : Assert(size == EstimateUncommittedEnumsSpace());
836 :
837 : /* Write out all the OIDs from the types hash table, if there is one. */
838 886 : if (uncommitted_enum_types)
839 : {
840 : HASH_SEQ_STATUS status;
841 : Oid *value;
842 :
843 0 : hash_seq_init(&status, uncommitted_enum_types);
844 0 : while ((value = (Oid *) hash_seq_search(&status)))
845 0 : *serialized++ = *value;
846 : }
847 :
848 : /* Write out the terminator. */
849 886 : *serialized++ = InvalidOid;
850 :
851 : /* Write out all the OIDs from the values hash table, if there is one. */
852 886 : if (uncommitted_enum_values)
853 : {
854 : HASH_SEQ_STATUS status;
855 : Oid *value;
856 :
857 0 : hash_seq_init(&status, uncommitted_enum_values);
858 0 : while ((value = (Oid *) hash_seq_search(&status)))
859 0 : *serialized++ = *value;
860 : }
861 :
862 : /* Write out the terminator. */
863 886 : *serialized++ = InvalidOid;
864 :
865 : /*
866 : * Make sure the amount of space we actually used matches what was
867 : * estimated.
868 : */
869 : Assert((char *) serialized == ((char *) space) + size);
870 886 : }
871 :
872 : void
873 2712 : RestoreUncommittedEnums(void *space)
874 : {
875 2712 : Oid *serialized = (Oid *) space;
876 :
877 : Assert(!uncommitted_enum_types);
878 : Assert(!uncommitted_enum_values);
879 :
880 : /*
881 : * If either list is empty then don't even bother to create that hash
882 : * table. This is the common case, since most transactions don't create
883 : * or alter enums.
884 : */
885 2712 : if (OidIsValid(*serialized))
886 : {
887 : /* Read all the types into a new hash table. */
888 0 : init_uncommitted_enum_types();
889 : do
890 : {
891 0 : (void) hash_search(uncommitted_enum_types, serialized++,
892 : HASH_ENTER, NULL);
893 0 : } while (OidIsValid(*serialized));
894 : }
895 2712 : serialized++;
896 2712 : if (OidIsValid(*serialized))
897 : {
898 : /* Read all the values into a new hash table. */
899 0 : init_uncommitted_enum_values();
900 : do
901 : {
902 0 : (void) hash_search(uncommitted_enum_values, serialized++,
903 : HASH_ENTER, NULL);
904 0 : } while (OidIsValid(*serialized));
905 : }
906 2712 : }
|