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