Age Owner TLA 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
4550 tgl 61 CBC 207 : EnumValuesCreate(Oid enumTypeOid, List *vals)
62 : {
63 : Relation pg_enum;
64 : Oid *oids;
65 : int elemno,
66 : num_elems;
67 : ListCell *lc;
144 michael 68 GNC 207 : int slotCount = 0;
69 : int nslots;
70 : CatalogIndexState indstate;
71 : TupleTableSlot **slot;
72 :
4854 bruce 73 CBC 207 : 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 :
1539 andres 81 207 : 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 : */
4854 bruce 91 207 : oids = (Oid *) palloc(num_elems * sizeof(Oid));
92 :
4550 tgl 93 125470 : 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 : {
1601 andres 104 250494 : new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
105 : Anum_pg_enum_oid);
4550 tgl 106 250494 : } while (new_oid & 1);
107 125263 : oids[elemno] = new_oid;
108 : }
109 :
110 : /* sort them, just in case OID counter wrapped from high to low */
111 207 : qsort(oids, num_elems, sizeof(Oid), oid_cmp);
112 :
113 : /* and make the entries */
144 michael 114 GNC 207 : indstate = CatalogOpenIndexes(pg_enum);
115 :
116 : /* allocate the slots to use and initialize them */
117 207 : nslots = Min(num_elems,
118 : MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_enum));
119 207 : slot = palloc(sizeof(TupleTableSlot *) * nslots);
120 108220 : for (int i = 0; i < nslots; i++)
121 108013 : slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_enum),
122 : &TTSOpsHeapTuple);
123 :
4854 bruce 124 GIC 207 : elemno = 0;
5851 tgl 125 CBC 125470 : foreach(lc, vals)
126 : {
5624 bruce 127 125263 : char *lab = strVal(lfirst(lc));
144 michael 128 GNC 125263 : Name enumlabel = palloc0(NAMEDATALEN);
5851 tgl 129 ECB :
5624 bruce 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.
5851 andrew 133 : */
5851 andrew 134 CBC 125263 : if (strlen(lab) > (NAMEDATALEN - 1))
5851 andrew 135 UIC 0 : ereport(ERROR,
5851 andrew 136 ECB : (errcode(ERRCODE_INVALID_NAME),
5558 alvherre 137 : errmsg("invalid enum label \"%s\"", lab),
138 : errdetail("Labels must be %d bytes or less.",
139 : NAMEDATALEN - 1)));
140 :
144 michael 141 GNC 125263 : ExecClearTuple(slot[slotCount]);
142 :
143 125263 : memset(slot[slotCount]->tts_isnull, false,
144 125263 : slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
145 :
146 125263 : slot[slotCount]->tts_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
147 125263 : slot[slotCount]->tts_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
148 125263 : slot[slotCount]->tts_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
149 :
150 125263 : namestrcpy(enumlabel, lab);
151 125263 : slot[slotCount]->tts_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(enumlabel);
152 :
153 125263 : ExecStoreVirtualTuple(slot[slotCount]);
154 125263 : slotCount++;
155 :
156 : /* if slots are full, insert a batch of tuples */
157 125263 : if (slotCount == nslots)
158 : {
159 204 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
160 : indstate);
161 204 : slotCount = 0;
162 : }
163 :
4854 bruce 164 CBC 125263 : elemno++;
5851 tgl 165 ECB : }
166 :
167 : /* Insert any tuples left in the buffer */
144 michael 168 GNC 207 : if (slotCount > 0)
169 125 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
170 : indstate);
171 :
5851 tgl 172 ECB : /* clean up */
5851 tgl 173 CBC 207 : pfree(oids);
144 michael 174 GNC 108220 : for (int i = 0; i < nslots; i++)
175 108013 : ExecDropSingleTupleTableSlot(slot[i]);
176 207 : CatalogCloseIndexes(indstate);
1539 andres 177 CBC 207 : table_close(pg_enum, RowExclusiveLock);
5851 tgl 178 GIC 207 : }
5851 tgl 179 ECB :
180 :
181 : /*
182 : * EnumValuesDelete
183 : * Remove all the pg_enum entries for the specified enum type.
184 : */
185 : void
5851 tgl 186 CBC 156 : EnumValuesDelete(Oid enumTypeOid)
187 : {
5851 tgl 188 ECB : Relation pg_enum;
189 : ScanKeyData key[1];
190 : SysScanDesc scan;
191 : HeapTuple tup;
192 :
1539 andres 193 CBC 156 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
194 :
5851 tgl 195 GIC 156 : ScanKeyInit(&key[0],
196 : Anum_pg_enum_enumtypid,
5851 tgl 197 ECB : BTEqualStrategyNumber, F_OIDEQ,
198 : ObjectIdGetDatum(enumTypeOid));
199 :
5851 tgl 200 GIC 156 : scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
201 : NULL, 1, key);
5851 tgl 202 ECB :
5851 tgl 203 CBC 125258 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
5851 tgl 204 ECB : {
2258 tgl 205 CBC 125102 : CatalogTupleDelete(pg_enum, &tup->t_self);
5851 tgl 206 ECB : }
207 :
5851 tgl 208 GIC 156 : systable_endscan(scan);
209 :
1539 andres 210 156 : table_close(pg_enum, RowExclusiveLock);
5851 tgl 211 156 : }
212 :
213 : /*
214 : * Initialize the uncommitted enum table for this transaction.
1643 tmunro 215 ECB : */
216 : static void
824 tmunro 217 GIC 123 : init_uncommitted_enums(void)
218 : {
219 : HASHCTL hash_ctl;
220 :
1643 221 123 : hash_ctl.keysize = sizeof(Oid);
1643 tmunro 222 CBC 123 : hash_ctl.entrysize = sizeof(Oid);
1643 tmunro 223 GIC 123 : hash_ctl.hcxt = TopTransactionContext;
824 tmunro 224 CBC 123 : uncommitted_enums = hash_create("Uncommitted enums",
225 : 32,
226 : &hash_ctl,
227 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
1643 tmunro 228 GIC 123 : }
5851 tgl 229 ECB :
230 : /*
231 : * AddEnumLabel
4550 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
4550 tgl 237 CBC 179 : AddEnumLabel(Oid enumTypeOid,
238 : const char *newVal,
4550 tgl 239 ECB : const char *neighbor,
3851 andrew 240 : bool newValIsAfter,
241 : bool skipIfExists)
242 : {
243 : Relation pg_enum;
244 : Oid newOid;
245 : Datum values[Natts_pg_enum];
4550 tgl 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 */
4550 tgl 256 GIC 179 : if (strlen(newVal) > (NAMEDATALEN - 1))
4550 tgl 257 CBC 3 : 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
4382 bruce 266 ECB : * 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 : */
4550 tgl 271 GIC 176 : 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 : */
3851 278 176 : enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
279 : ObjectIdGetDatum(enumTypeOid),
280 : CStringGetDatum(newVal));
281 176 : if (HeapTupleIsValid(enum_tup))
282 : {
283 6 : ReleaseSysCache(enum_tup);
284 6 : if (skipIfExists)
3851 andrew 285 ECB : {
3851 tgl 286 CBC 3 : ereport(NOTICE,
287 : (errcode(ERRCODE_DUPLICATE_OBJECT),
288 : errmsg("enum label \"%s\" already exists, skipping",
289 : newVal)));
3851 andrew 290 GIC 3 : return;
291 : }
292 : else
tgl 293 3 : ereport(ERROR,
294 : (errcode(ERRCODE_DUPLICATE_OBJECT),
295 : errmsg("enum label \"%s\" already exists",
296 : newVal)));
297 : }
298 :
1539 andres 299 170 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
4550 tgl 300 ECB :
301 : /* If we have to renumber the existing members, we restart from here */
4550 tgl 302 GIC 173 : restart:
303 :
304 : /* Get the list of existing members of the enum */
305 173 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
306 : ObjectIdGetDatum(enumTypeOid));
4382 bruce 307 CBC 173 : nelems = list->n_members;
308 :
309 : /* Sort the existing members by enumsortorder */
4550 tgl 310 173 : existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
4550 tgl 311 GIC 2419 : for (i = 0; i < nelems; i++)
4550 tgl 312 CBC 2246 : existing[i] = &(list->members[i]->tuple);
4550 tgl 313 ECB :
4550 tgl 314 GIC 173 : qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
4550 tgl 315 ECB :
4550 tgl 316 GIC 173 : if (neighbor == NULL)
317 : {
318 : /*
4382 bruce 319 ECB : * Put the new label at the end of the list. No change to existing
320 : * tuples is required.
321 : */
4550 tgl 322 CBC 62 : if (nelems > 0)
323 : {
4550 tgl 324 GIC 59 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
325 :
326 59 : newelemorder = en->enumsortorder + 1;
327 : }
4550 tgl 328 ECB : else
4550 tgl 329 GIC 3 : newelemorder = 1;
330 : }
4550 tgl 331 ECB : else
332 : {
333 : /* BEFORE or AFTER was specified */
4382 bruce 334 : int nbr_index;
335 : int other_nbr_index;
336 : Form_pg_enum nbr_en;
337 : Form_pg_enum other_nbr_en;
338 :
4550 tgl 339 : /* Locate the neighbor element */
4550 tgl 340 CBC 1643 : for (nbr_index = 0; nbr_index < nelems; nbr_index++)
4550 tgl 341 ECB : {
4550 tgl 342 GIC 1640 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
4550 tgl 343 ECB :
4550 tgl 344 GIC 1640 : if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
4550 tgl 345 CBC 108 : break;
346 : }
4550 tgl 347 GIC 111 : if (nbr_index >= nelems)
348 3 : ereport(ERROR,
349 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
350 : errmsg("\"%s\" is not an existing enum label",
4550 tgl 351 ECB : neighbor)));
4550 tgl 352 GIC 108 : nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
4550 tgl 353 ECB :
354 : /*
4382 bruce 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.
4550 tgl 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 : */
4550 tgl 364 GIC 108 : if (newValIsAfter)
365 8 : other_nbr_index = nbr_index + 1;
366 : else
367 100 : other_nbr_index = nbr_index - 1;
368 :
4550 tgl 369 CBC 108 : if (other_nbr_index < 0)
4550 tgl 370 GIC 4 : newelemorder = nbr_en->enumsortorder - 1;
4550 tgl 371 CBC 104 : else if (other_nbr_index >= nelems)
4550 tgl 372 GIC 4 : newelemorder = nbr_en->enumsortorder + 1;
4550 tgl 373 ECB : else
374 : {
375 : /*
2412 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.
4549 381 : */
382 : volatile float4 midpoint;
383 :
2412 tgl 384 GIC 100 : other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
385 100 : midpoint = (nbr_en->enumsortorder +
386 100 : other_nbr_en->enumsortorder) / 2;
387 :
388 100 : if (midpoint == nbr_en->enumsortorder ||
389 97 : midpoint == other_nbr_en->enumsortorder)
390 : {
4550 391 3 : RenumberEnumType(pg_enum, existing, nelems);
392 : /* Clean up and start over */
4550 tgl 393 CBC 3 : pfree(existing);
394 3 : ReleaseCatCacheList(list);
4550 tgl 395 GIC 3 : goto restart;
4550 tgl 396 ECB : }
397 :
2412 tgl 398 CBC 97 : newelemorder = midpoint;
4550 tgl 399 ECB : }
400 : }
401 :
402 : /* Get a new OID for the new label */
3149 bruce 403 GIC 167 : if (IsBinaryUpgrade)
404 : {
405 44 : if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
3149 bruce 406 UIC 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
4382 bruce 413 ECB : * guaranteed to be consistent.
4550 tgl 414 : */
4550 tgl 415 CBC 44 : if (neighbor != NULL)
4550 tgl 416 UIC 0 : ereport(ERROR,
4550 tgl 417 ECB : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
418 : errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
419 :
4550 tgl 420 CBC 44 : newOid = binary_upgrade_next_pg_enum_oid;
4550 tgl 421 GIC 44 : binary_upgrade_next_pg_enum_oid = InvalidOid;
4550 tgl 422 ECB : }
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 (;;)
4550 tgl 433 GIC 91 : {
4382 bruce 434 ECB : bool sorts_ok;
4550 tgl 435 EUB :
436 : /* Get a new OID (different from all existing pg_enum tuples) */
1601 andres 437 GIC 214 : 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
4382 bruce 444 ECB : * will not take the fast path anyway.
4550 tgl 445 EUB : */
4550 tgl 446 GIC 214 : sorts_ok = true;
447 2873 : for (i = 0; i < nelems; i++)
448 : {
4550 tgl 449 CBC 2838 : HeapTuple exists_tup = existing[i];
450 2838 : Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
1601 andres 451 GIC 2838 : Oid exists_oid = exists_en->oid;
452 :
4550 tgl 453 2838 : if (exists_oid & 1)
454 2380 : continue; /* ignore odd Oids */
455 :
456 458 : if (exists_en->enumsortorder < newelemorder)
457 : {
458 : /* should sort before */
459 279 : if (exists_oid >= newOid)
460 : {
4550 tgl 461 UIC 0 : sorts_ok = false;
4550 tgl 462 LBC 0 : break;
463 : }
464 : }
465 : else
4550 tgl 466 ECB : {
467 : /* should sort after */
4550 tgl 468 GIC 179 : if (exists_oid <= newOid)
469 : {
470 179 : sorts_ok = false;
471 179 : break;
472 : }
473 : }
474 : }
4550 tgl 475 ECB :
4550 tgl 476 CBC 214 : if (sorts_ok)
477 : {
4550 tgl 478 ECB : /* If it's even and sorts OK, we're done. */
4550 tgl 479 CBC 35 : if ((newOid & 1) == 0)
480 22 : break;
481 :
4550 tgl 482 ECB : /*
4382 bruce 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 : }
4550 tgl 488 : else
489 : {
4550 tgl 490 EUB : /*
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 : */
4550 tgl 495 GIC 179 : if (newOid & 1)
496 101 : break;
4550 tgl 497 ECB :
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 */
4550 tgl 507 GIC 167 : pfree(existing);
4550 tgl 508 CBC 167 : ReleaseCatCacheList(list);
4550 tgl 509 ECB :
510 : /* Create the new pg_enum entry */
4550 tgl 511 GIC 167 : memset(nulls, false, sizeof(nulls));
1601 andres 512 167 : values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
4550 tgl 513 167 : values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
514 167 : values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
515 167 : namestrcpy(&enumlabel, newVal);
516 167 : values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
517 167 : enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
2259 alvherre 518 167 : CatalogTupleInsert(pg_enum, enum_tup);
4550 tgl 519 167 : heap_freetuple(enum_tup);
520 :
1539 andres 521 167 : table_close(pg_enum, RowExclusiveLock);
522 :
523 : /* Set up the uncommitted enum table if not already done in this tx */
824 tmunro 524 CBC 167 : if (uncommitted_enums == NULL)
525 123 : init_uncommitted_enums();
526 :
527 : /* Add the new value to the table */
824 tmunro 528 GIC 167 : (void) hash_search(uncommitted_enums, &newOid, HASH_ENTER, NULL);
529 : }
530 :
531 :
532 : /*
533 : * RenameEnumLabel
534 : * Rename a label in an enum set.
535 : */
2405 tgl 536 ECB : void
2405 tgl 537 CBC 12 : RenameEnumLabel(Oid enumTypeOid,
538 : const char *oldVal,
539 : const char *newVal)
2405 tgl 540 ECB : {
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 */
2405 tgl 551 GIC 12 : if (strlen(newVal) > (NAMEDATALEN - 1))
2405 tgl 552 UIC 0 : ereport(ERROR,
2405 tgl 553 ECB : (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 : */
2405 tgl 565 GIC 12 : LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
2405 tgl 566 ECB :
1539 andres 567 GIC 12 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
568 :
569 : /* Get the list of existing members of the enum */
2405 tgl 570 12 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
571 : ObjectIdGetDatum(enumTypeOid));
572 12 : 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 12 : old_tup = NULL;
2405 tgl 580 CBC 12 : found_new = false;
2405 tgl 581 GBC 72 : for (i = 0; i < nelems; i++)
582 : {
2405 tgl 583 GIC 60 : enum_tup = &(list->members[i]->tuple);
584 60 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
585 60 : if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
586 9 : old_tup = enum_tup;
587 60 : if (strcmp(NameStr(en->enumlabel), newVal) == 0)
588 6 : found_new = true;
589 : }
590 12 : if (!old_tup)
591 3 : ereport(ERROR,
592 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
593 : errmsg("\"%s\" is not an existing enum label",
2405 tgl 594 ECB : oldVal)));
2405 tgl 595 GIC 9 : if (found_new)
2405 tgl 596 CBC 3 : ereport(ERROR,
597 : (errcode(ERRCODE_DUPLICATE_OBJECT),
598 : errmsg("enum label \"%s\" already exists",
2405 tgl 599 ECB : newVal)));
600 :
601 : /* OK, make a writable copy of old tuple */
2405 tgl 602 GIC 6 : enum_tup = heap_copytuple(old_tup);
603 6 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
604 :
605 6 : ReleaseCatCacheList(list);
606 :
607 : /* Update the pg_enum entry */
2405 tgl 608 CBC 6 : namestrcpy(&en->enumlabel, newVal);
2259 alvherre 609 6 : CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
2405 tgl 610 6 : heap_freetuple(enum_tup);
611 :
1539 andres 612 6 : table_close(pg_enum, RowExclusiveLock);
2405 tgl 613 6 : }
2405 tgl 614 ECB :
615 :
1643 tmunro 616 : /*
824 617 : * Test if the given enum value is in the table of uncommitted enums.
618 : */
1643 619 : bool
824 tmunro 620 CBC 39 : EnumUncommitted(Oid enum_id)
621 : {
622 : bool found;
623 :
824 tmunro 624 ECB : /* If we've made no uncommitted table, all values are safe */
824 tmunro 625 CBC 39 : if (uncommitted_enums == NULL)
1643 tmunro 626 GIC 21 : return false;
627 :
628 : /* Else, is it in the table? */
824 629 18 : (void) hash_search(uncommitted_enums, &enum_id, HASH_FIND, &found);
1643 630 18 : return found;
1643 tmunro 631 ECB : }
632 :
633 :
634 : /*
635 : * Clean up enum stuff after end of top-level transaction.
636 : */
637 : void
1643 tmunro 638 CBC 485768 : AtEOXact_Enum(void)
1643 tmunro 639 ECB : {
640 : /*
824 641 : * Reset the uncommitted table, as all our enum values are now committed.
1643 642 : * The memory will go away automatically when TopTransactionContext is
643 : * freed; it's sufficient to clear our pointer.
644 : */
824 tmunro 645 GIC 485768 : uncommitted_enums = NULL;
1643 646 485768 : }
647 :
648 :
4550 tgl 649 ECB : /*
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
3511 rhaas 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
4550 tgl 672 GIC 3 : RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
673 : {
4550 tgl 674 ECB : 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 : */
4550 tgl 681 GIC 78 : for (i = nelems - 1; i >= 0; i--)
682 : {
683 : HeapTuple newtup;
684 : Form_pg_enum en;
685 : float4 newsortorder;
686 :
687 75 : newtup = heap_copytuple(existing[i]);
688 75 : en = (Form_pg_enum) GETSTRUCT(newtup);
689 :
690 75 : newsortorder = i + 1;
691 75 : if (en->enumsortorder != newsortorder)
692 : {
693 72 : en->enumsortorder = newsortorder;
694 :
2259 alvherre 695 72 : CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
696 : }
697 :
4550 tgl 698 75 : heap_freetuple(newtup);
699 : }
700 :
4550 tgl 701 ECB : /* Make the updates visible */
4550 tgl 702 GIC 3 : CommandCounterIncrement();
703 3 : }
704 :
705 :
706 : /* qsort comparison function for tuples by sort order */
707 : static int
708 8804 : sort_order_cmp(const void *p1, const void *p2)
709 : {
4382 bruce 710 CBC 8804 : HeapTuple v1 = *((const HeapTuple *) p1);
4382 bruce 711 GIC 8804 : HeapTuple v2 = *((const HeapTuple *) p2);
712 8804 : Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
713 8804 : Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
714 :
4550 tgl 715 8804 : if (en1->enumsortorder < en2->enumsortorder)
4550 tgl 716 CBC 3732 : return -1;
717 5072 : else if (en1->enumsortorder > en2->enumsortorder)
4550 tgl 718 GIC 5072 : return 1;
4550 tgl 719 ECB : else
4550 tgl 720 LBC 0 : return 0;
721 : }
1643 tmunro 722 ECB :
723 : Size
824 tmunro 724 CBC 806 : EstimateUncommittedEnumsSpace(void)
725 : {
726 : size_t entries;
1643 tmunro 727 ECB :
824 tmunro 728 GIC 806 : if (uncommitted_enums)
824 tmunro 729 UIC 0 : entries = hash_get_num_entries(uncommitted_enums);
730 : else
1643 tmunro 731 CBC 806 : entries = 0;
1643 tmunro 732 ECB :
733 : /* Add one for the terminator. */
1643 tmunro 734 GIC 806 : return sizeof(Oid) * (entries + 1);
735 : }
736 :
1643 tmunro 737 ECB : void
824 tmunro 738 GIC 403 : SerializeUncommittedEnums(void *space, Size size)
1643 tmunro 739 ECB : {
1643 tmunro 740 CBC 403 : Oid *serialized = (Oid *) space;
1643 tmunro 741 ECB :
742 : /*
743 : * Make sure the hash table hasn't changed in size since the caller
744 : * reserved the space.
745 : */
824 tmunro 746 CBC 403 : Assert(size == EstimateUncommittedEnumsSpace());
1643 tmunro 747 ECB :
748 : /* Write out all the values from the hash table, if there is one. */
824 tmunro 749 GBC 403 : if (uncommitted_enums)
750 : {
751 : HASH_SEQ_STATUS status;
752 : Oid *value;
1643 tmunro 753 ECB :
824 tmunro 754 UIC 0 : hash_seq_init(&status, uncommitted_enums);
1643 755 0 : while ((value = (Oid *) hash_seq_search(&status)))
756 0 : *serialized++ = *value;
1643 tmunro 757 ECB : }
1643 tmunro 758 EUB :
759 : /* Write out the terminator. */
1643 tmunro 760 CBC 403 : *serialized = InvalidOid;
761 :
762 : /*
1643 tmunro 763 ECB : * Make sure the amount of space we actually used matches what was
764 : * estimated.
765 : */
1643 tmunro 766 GIC 403 : Assert((char *) (serialized + 1) == ((char *) space) + size);
1643 tmunro 767 CBC 403 : }
768 :
1643 tmunro 769 ECB : void
824 tmunro 770 GIC 1298 : RestoreUncommittedEnums(void *space)
771 : {
1643 772 1298 : Oid *serialized = (Oid *) space;
773 :
824 774 1298 : Assert(!uncommitted_enums);
1643 tmunro 775 ECB :
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 : */
1643 tmunro 781 GIC 1298 : if (!OidIsValid(*serialized))
782 1298 : return;
1643 tmunro 783 EUB :
784 : /* Read all the values into a new hash table. */
824 tmunro 785 UBC 0 : init_uncommitted_enums();
786 : do
787 : {
824 tmunro 788 UIC 0 : hash_search(uncommitted_enums, serialized++, HASH_ENTER, NULL);
1643 tmunro 789 LBC 0 : } while (OidIsValid(*serialized));
790 : }
|