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