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-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
4921 tgl@sss.pgh.pa.us 84 :CBC 211 : EnumValuesCreate(Oid enumTypeOid, List *vals)
85 : : {
86 : : Relation pg_enum;
87 : : Oid *oids;
88 : : int elemno,
89 : : num_elems;
90 : : ListCell *lc;
515 michael@paquier.xyz 91 : 211 : 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 : : */
21 tgl@sss.pgh.pa.us 103 [ + - ]:GNC 211 : if (GetCurrentTransactionNestLevel() == 1)
104 : : {
105 [ + + ]: 211 : if (uncommitted_enum_types == NULL)
106 : 208 : init_uncommitted_enum_types();
107 : 211 : (void) hash_search(uncommitted_enum_types, &enumTypeOid,
108 : : HASH_ENTER, NULL);
109 : : }
110 : :
5225 bruce@momjian.us 111 :CBC 211 : 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 : :
1910 andres@anarazel.de 119 : 211 : 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 : : */
5225 bruce@momjian.us 129 : 211 : oids = (Oid *) palloc(num_elems * sizeof(Oid));
130 : :
4921 tgl@sss.pgh.pa.us 131 [ + + ]: 125492 : 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 : : {
1972 andres@anarazel.de 142 : 250522 : new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
143 : : Anum_pg_enum_oid);
4921 tgl@sss.pgh.pa.us 144 [ + + ]: 250522 : } while (new_oid & 1);
145 : 125281 : oids[elemno] = new_oid;
146 : : }
147 : :
148 : : /* sort them, just in case OID counter wrapped from high to low */
149 : 211 : qsort(oids, num_elems, sizeof(Oid), oid_cmp);
150 : :
151 : : /* and make the entries */
515 michael@paquier.xyz 152 : 211 : indstate = CatalogOpenIndexes(pg_enum);
153 : :
154 : : /* allocate the slots to use and initialize them */
155 [ + + ]: 211 : nslots = Min(num_elems,
156 : : MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_enum));
157 : 211 : slot = palloc(sizeof(TupleTableSlot *) * nslots);
158 [ + + ]: 108242 : for (int i = 0; i < nslots; i++)
159 : 108031 : slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_enum),
160 : : &TTSOpsHeapTuple);
161 : :
5225 bruce@momjian.us 162 : 211 : elemno = 0;
6222 tgl@sss.pgh.pa.us 163 [ + + + + : 125492 : foreach(lc, vals)
+ + ]
164 : : {
5995 bruce@momjian.us 165 : 125281 : char *lab = strVal(lfirst(lc));
515 michael@paquier.xyz 166 : 125281 : 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 : : */
6222 andrew@dunslane.net 172 [ - + ]: 125281 : if (strlen(lab) > (NAMEDATALEN - 1))
6222 andrew@dunslane.net 173 [ # # ]:UBC 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 : :
515 michael@paquier.xyz 179 :CBC 125281 : ExecClearTuple(slot[slotCount]);
180 : :
181 : 125281 : memset(slot[slotCount]->tts_isnull, false,
182 : 125281 : slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
183 : :
184 : 125281 : slot[slotCount]->tts_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
185 : 125281 : slot[slotCount]->tts_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
186 : 125281 : slot[slotCount]->tts_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
187 : :
188 : 125281 : namestrcpy(enumlabel, lab);
189 : 125281 : slot[slotCount]->tts_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(enumlabel);
190 : :
191 : 125281 : ExecStoreVirtualTuple(slot[slotCount]);
192 : 125281 : slotCount++;
193 : :
194 : : /* if slots are full, insert a batch of tuples */
195 [ + + ]: 125281 : if (slotCount == nslots)
196 : : {
197 : 207 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
198 : : indstate);
199 : 207 : slotCount = 0;
200 : : }
201 : :
5225 bruce@momjian.us 202 : 125281 : elemno++;
203 : : }
204 : :
205 : : /* Insert any tuples left in the buffer */
515 michael@paquier.xyz 206 [ + + ]: 211 : if (slotCount > 0)
207 : 125 : CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
208 : : indstate);
209 : :
210 : : /* clean up */
6222 tgl@sss.pgh.pa.us 211 : 211 : pfree(oids);
515 michael@paquier.xyz 212 [ + + ]: 108242 : for (int i = 0; i < nslots; i++)
213 : 108031 : ExecDropSingleTupleTableSlot(slot[i]);
214 : 211 : CatalogCloseIndexes(indstate);
1910 andres@anarazel.de 215 : 211 : table_close(pg_enum, RowExclusiveLock);
6222 tgl@sss.pgh.pa.us 216 : 211 : }
217 : :
218 : :
219 : : /*
220 : : * EnumValuesDelete
221 : : * Remove all the pg_enum entries for the specified enum type.
222 : : */
223 : : void
224 : 156 : EnumValuesDelete(Oid enumTypeOid)
225 : : {
226 : : Relation pg_enum;
227 : : ScanKeyData key[1];
228 : : SysScanDesc scan;
229 : : HeapTuple tup;
230 : :
1910 andres@anarazel.de 231 : 156 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
232 : :
6222 tgl@sss.pgh.pa.us 233 : 156 : ScanKeyInit(&key[0],
234 : : Anum_pg_enum_enumtypid,
235 : : BTEqualStrategyNumber, F_OIDEQ,
236 : : ObjectIdGetDatum(enumTypeOid));
237 : :
238 : 156 : scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
239 : : NULL, 1, key);
240 : :
241 [ + + ]: 125258 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
242 : : {
2629 243 : 125102 : CatalogTupleDelete(pg_enum, &tup->t_self);
244 : : }
245 : :
6222 246 : 156 : systable_endscan(scan);
247 : :
1910 andres@anarazel.de 248 : 156 : table_close(pg_enum, RowExclusiveLock);
6222 tgl@sss.pgh.pa.us 249 : 156 : }
250 : :
251 : : /*
252 : : * Initialize the uncommitted enum types table for this transaction.
253 : : */
254 : : static void
21 tgl@sss.pgh.pa.us 255 :GNC 208 : init_uncommitted_enum_types(void)
256 : : {
257 : : HASHCTL hash_ctl;
258 : :
21 tgl@sss.pgh.pa.us 259 :CBC 208 : hash_ctl.keysize = sizeof(Oid);
260 : 208 : hash_ctl.entrysize = sizeof(Oid);
261 : 208 : hash_ctl.hcxt = TopTransactionContext;
21 tgl@sss.pgh.pa.us 262 :GNC 208 : uncommitted_enum_types = hash_create("Uncommitted enum types",
263 : : 32,
264 : : &hash_ctl,
265 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
266 : 208 : }
267 : :
268 : : /*
269 : : * Initialize the uncommitted enum values table for this transaction.
270 : : */
271 : : static void
272 : 117 : init_uncommitted_enum_values(void)
273 : : {
274 : : HASHCTL hash_ctl;
275 : :
2014 tmunro@postgresql.or 276 : 117 : hash_ctl.keysize = sizeof(Oid);
277 : 117 : hash_ctl.entrysize = sizeof(Oid);
278 : 117 : hash_ctl.hcxt = TopTransactionContext;
21 tgl@sss.pgh.pa.us 279 : 117 : uncommitted_enum_values = hash_create("Uncommitted enum values",
280 : : 32,
281 : : &hash_ctl,
282 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
2014 tmunro@postgresql.or 283 :CBC 117 : }
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
4921 tgl@sss.pgh.pa.us 292 : 185 : 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 [ + + ]: 185 : if (strlen(newVal) > (NAMEDATALEN - 1))
312 [ + - ]: 3 : 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 : 182 : 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 : : */
4222 333 : 182 : enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
334 : : ObjectIdGetDatum(enumTypeOid),
335 : : CStringGetDatum(newVal));
336 [ + + ]: 182 : if (HeapTupleIsValid(enum_tup))
337 : : {
338 : 6 : ReleaseSysCache(enum_tup);
339 [ + + ]: 6 : if (skipIfExists)
340 : : {
341 [ + - ]: 3 : ereport(NOTICE,
342 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
343 : : errmsg("enum label \"%s\" already exists, skipping",
344 : : newVal)));
andrew@dunslane.net 345 : 59 : return;
346 : : }
347 : : else
tgl@sss.pgh.pa.us 348 [ + - ]: 3 : ereport(ERROR,
349 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
350 : : errmsg("enum label \"%s\" already exists",
351 : : newVal)));
352 : : }
353 : :
1910 andres@anarazel.de 354 : 176 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
355 : :
356 : : /* If we have to renumber the existing members, we restart from here */
4921 tgl@sss.pgh.pa.us 357 : 179 : restart:
358 : :
359 : : /* Get the list of existing members of the enum */
360 : 179 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
361 : : ObjectIdGetDatum(enumTypeOid));
4753 bruce@momjian.us 362 : 179 : nelems = list->n_members;
363 : :
364 : : /* Sort the existing members by enumsortorder */
4921 tgl@sss.pgh.pa.us 365 : 179 : existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
366 [ + + ]: 2440 : for (i = 0; i < nelems; i++)
367 : 2261 : existing[i] = &(list->members[i]->tuple);
368 : :
369 : 179 : qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
370 : :
371 [ + + ]: 179 : 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 [ + + ]: 68 : if (nelems > 0)
378 : : {
379 : 64 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
380 : :
381 : 64 : newelemorder = en->enumsortorder + 1;
382 : : }
383 : : else
384 : 4 : 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 [ + + ]: 1643 : for (nbr_index = 0; nbr_index < nelems; nbr_index++)
396 : : {
397 : 1640 : Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
398 : :
399 [ + + ]: 1640 : if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
400 : 108 : break;
401 : : }
402 [ + + ]: 111 : if (nbr_index >= nelems)
403 [ + - ]: 3 : ereport(ERROR,
404 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
405 : : errmsg("\"%s\" is not an existing enum label",
406 : : neighbor)));
407 : 108 : 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 [ + + ]: 108 : if (newValIsAfter)
420 : 8 : other_nbr_index = nbr_index + 1;
421 : : else
422 : 100 : other_nbr_index = nbr_index - 1;
423 : :
424 [ + + ]: 108 : if (other_nbr_index < 0)
425 : 4 : newelemorder = nbr_en->enumsortorder - 1;
426 [ + + ]: 104 : else if (other_nbr_index >= nelems)
427 : 4 : 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 : :
2783 439 : 100 : other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
440 : 100 : midpoint = (nbr_en->enumsortorder +
441 : 100 : other_nbr_en->enumsortorder) / 2;
442 : :
443 [ + + ]: 100 : if (midpoint == nbr_en->enumsortorder ||
444 [ - + ]: 97 : midpoint == other_nbr_en->enumsortorder)
445 : : {
4921 446 : 3 : RenumberEnumType(pg_enum, existing, nelems);
447 : : /* Clean up and start over */
448 : 3 : pfree(existing);
449 : 3 : ReleaseCatCacheList(list);
450 : 3 : goto restart;
451 : : }
452 : :
2783 453 : 97 : newelemorder = midpoint;
454 : : }
455 : : }
456 : :
457 : : /* Get a new OID for the new label */
3520 bruce@momjian.us 458 [ + + ]: 173 : if (IsBinaryUpgrade)
459 : : {
460 [ - + ]: 50 : if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
3520 bruce@momjian.us 461 [ # # ]:UBC 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 : : */
4921 tgl@sss.pgh.pa.us 470 [ - + ]:CBC 50 : if (neighbor != NULL)
4921 tgl@sss.pgh.pa.us 471 [ # # ]:UBC 0 : ereport(ERROR,
472 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
473 : : errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
474 : :
4921 tgl@sss.pgh.pa.us 475 :CBC 50 : newOid = binary_upgrade_next_pg_enum_oid;
476 : 50 : 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 : 82 : {
489 : : bool sorts_ok;
490 : :
491 : : /* Get a new OID (different from all existing pg_enum tuples) */
1972 andres@anarazel.de 492 : 205 : 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 : : */
4921 tgl@sss.pgh.pa.us 501 : 205 : sorts_ok = true;
502 [ + + ]: 2824 : for (i = 0; i < nelems; i++)
503 : : {
504 : 2788 : HeapTuple exists_tup = existing[i];
505 : 2788 : Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
1972 andres@anarazel.de 506 : 2788 : Oid exists_oid = exists_en->oid;
507 : :
4921 tgl@sss.pgh.pa.us 508 [ + + ]: 2788 : if (exists_oid & 1)
509 : 2351 : continue; /* ignore odd Oids */
510 : :
511 [ + + ]: 437 : if (exists_en->enumsortorder < newelemorder)
512 : : {
513 : : /* should sort before */
514 [ - + ]: 268 : if (exists_oid >= newOid)
515 : : {
4921 tgl@sss.pgh.pa.us 516 :UBC 0 : sorts_ok = false;
517 : 0 : break;
518 : : }
519 : : }
520 : : else
521 : : {
522 : : /* should sort after */
4921 tgl@sss.pgh.pa.us 523 [ + - ]:CBC 169 : if (exists_oid <= newOid)
524 : : {
525 : 169 : sorts_ok = false;
526 : 169 : break;
527 : : }
528 : : }
529 : : }
530 : :
531 [ + + ]: 205 : if (sorts_ok)
532 : : {
533 : : /* If it's even and sorts OK, we're done. */
534 [ + + ]: 36 : if ((newOid & 1) == 0)
535 : 22 : 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 [ + + ]: 169 : if (newOid & 1)
551 : 101 : 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 : 173 : pfree(existing);
563 : 173 : ReleaseCatCacheList(list);
564 : :
565 : : /* Create the new pg_enum entry */
566 : 173 : memset(nulls, false, sizeof(nulls));
1972 andres@anarazel.de 567 : 173 : values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
4921 tgl@sss.pgh.pa.us 568 : 173 : values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
569 : 173 : values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
570 : 173 : namestrcpy(&enumlabel, newVal);
571 : 173 : values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
572 : 173 : enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
2630 alvherre@alvh.no-ip. 573 : 173 : CatalogTupleInsert(pg_enum, enum_tup);
4921 tgl@sss.pgh.pa.us 574 : 173 : heap_freetuple(enum_tup);
575 : :
1910 andres@anarazel.de 576 : 173 : 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 : : */
21 tgl@sss.pgh.pa.us 589 [ + - + + ]:GNC 346 : if (GetCurrentTransactionNestLevel() == 1 &&
590 : 173 : EnumTypeUncommitted(enumTypeOid))
591 : 56 : return;
592 : :
593 : : /* Set up the uncommitted values table if not already done in this tx */
594 [ + - ]: 117 : if (uncommitted_enum_values == NULL)
595 : 117 : init_uncommitted_enum_values();
596 : :
597 : : /* Add the new value to the table */
598 : 117 : (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
2776 tgl@sss.pgh.pa.us 607 :CBC 12 : 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 [ - + ]: 12 : if (strlen(newVal) > (NAMEDATALEN - 1))
2776 tgl@sss.pgh.pa.us 622 [ # # ]:UBC 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 : : */
2776 tgl@sss.pgh.pa.us 635 :CBC 12 : LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
636 : :
1910 andres@anarazel.de 637 : 12 : pg_enum = table_open(EnumRelationId, RowExclusiveLock);
638 : :
639 : : /* Get the list of existing members of the enum */
2776 tgl@sss.pgh.pa.us 640 : 12 : list = SearchSysCacheList1(ENUMTYPOIDNAME,
641 : : ObjectIdGetDatum(enumTypeOid));
642 : 12 : 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 : 12 : old_tup = NULL;
650 : 12 : found_new = false;
651 [ + + ]: 72 : for (i = 0; i < nelems; i++)
652 : : {
653 : 60 : enum_tup = &(list->members[i]->tuple);
654 : 60 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
655 [ + + ]: 60 : if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
656 : 9 : old_tup = enum_tup;
657 [ + + ]: 60 : if (strcmp(NameStr(en->enumlabel), newVal) == 0)
658 : 6 : found_new = true;
659 : : }
660 [ + + ]: 12 : if (!old_tup)
661 [ + - ]: 3 : ereport(ERROR,
662 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
663 : : errmsg("\"%s\" is not an existing enum label",
664 : : oldVal)));
665 [ + + ]: 9 : if (found_new)
666 [ + - ]: 3 : 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 : 6 : enum_tup = heap_copytuple(old_tup);
673 : 6 : en = (Form_pg_enum) GETSTRUCT(enum_tup);
674 : :
675 : 6 : ReleaseCatCacheList(list);
676 : :
677 : : /* Update the pg_enum entry */
678 : 6 : namestrcpy(&en->enumlabel, newVal);
2630 alvherre@alvh.no-ip. 679 : 6 : CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
2776 tgl@sss.pgh.pa.us 680 : 6 : heap_freetuple(enum_tup);
681 : :
1910 andres@anarazel.de 682 : 6 : table_close(pg_enum, RowExclusiveLock);
2776 tgl@sss.pgh.pa.us 683 : 6 : }
684 : :
685 : :
686 : : /*
687 : : * Test if the given type OID is in the table of uncommitted enum types.
688 : : */
689 : : static bool
21 tgl@sss.pgh.pa.us 690 :GNC 173 : 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 [ + + ]: 173 : if (uncommitted_enum_types == NULL)
696 : 117 : return false;
697 : :
698 : : /* Else, is it in the table? */
699 : 56 : (void) hash_search(uncommitted_enum_types, &typ_id, HASH_FIND, &found);
700 : 56 : return found;
701 : : }
702 : :
703 : :
704 : : /*
705 : : * Test if the given enum value is in the table of uncommitted enum values.
706 : : */
707 : : bool
1195 tmunro@postgresql.or 708 :CBC 45 : EnumUncommitted(Oid enum_id)
709 : : {
710 : : bool found;
711 : :
712 : : /* If we've made no uncommitted values table, it's not in the table */
21 tgl@sss.pgh.pa.us 713 [ + + ]:GNC 45 : if (uncommitted_enum_values == NULL)
2014 tmunro@postgresql.or 714 :CBC 33 : return false;
715 : :
716 : : /* Else, is it in the table? */
21 tgl@sss.pgh.pa.us 717 :GNC 12 : (void) hash_search(uncommitted_enum_values, &enum_id, HASH_FIND, &found);
2014 tmunro@postgresql.or 718 :CBC 12 : return found;
719 : : }
720 : :
721 : :
722 : : /*
723 : : * Clean up enum stuff after end of top-level transaction.
724 : : */
725 : : void
726 : 431446 : 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 : : */
21 tgl@sss.pgh.pa.us 733 :GNC 431446 : uncommitted_enum_types = NULL;
734 : 431446 : uncommitted_enum_values = NULL;
2014 tmunro@postgresql.or 735 :CBC 431446 : }
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
4921 tgl@sss.pgh.pa.us 761 : 3 : 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 [ + + ]: 78 : for (i = nelems - 1; i >= 0; i--)
771 : : {
772 : : HeapTuple newtup;
773 : : Form_pg_enum en;
774 : : float4 newsortorder;
775 : :
776 : 75 : newtup = heap_copytuple(existing[i]);
777 : 75 : en = (Form_pg_enum) GETSTRUCT(newtup);
778 : :
779 : 75 : newsortorder = i + 1;
780 [ + + ]: 75 : if (en->enumsortorder != newsortorder)
781 : : {
782 : 72 : en->enumsortorder = newsortorder;
783 : :
2630 alvherre@alvh.no-ip. 784 : 72 : CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
785 : : }
786 : :
4921 tgl@sss.pgh.pa.us 787 : 75 : heap_freetuple(newtup);
788 : : }
789 : :
790 : : /* Make the updates visible */
791 : 3 : CommandCounterIncrement();
792 : 3 : }
793 : :
794 : :
795 : : /* qsort comparison function for tuples by sort order */
796 : : static int
797 : 8821 : sort_order_cmp(const void *p1, const void *p2)
798 : : {
4753 bruce@momjian.us 799 : 8821 : HeapTuple v1 = *((const HeapTuple *) p1);
800 : 8821 : HeapTuple v2 = *((const HeapTuple *) p2);
801 : 8821 : Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
802 : 8821 : Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
803 : :
4921 tgl@sss.pgh.pa.us 804 [ + + ]: 8821 : if (en1->enumsortorder < en2->enumsortorder)
805 : 3735 : return -1;
806 [ + - ]: 5086 : else if (en1->enumsortorder > en2->enumsortorder)
807 : 5086 : return 1;
808 : : else
4921 tgl@sss.pgh.pa.us 809 :UBC 0 : return 0;
810 : : }
811 : :
812 : : Size
1195 tmunro@postgresql.or 813 :CBC 828 : EstimateUncommittedEnumsSpace(void)
814 : : {
21 tgl@sss.pgh.pa.us 815 :GNC 828 : size_t entries = 0;
816 : :
817 [ - + ]: 828 : if (uncommitted_enum_types)
21 tgl@sss.pgh.pa.us 818 :UNC 0 : entries += hash_get_num_entries(uncommitted_enum_types);
21 tgl@sss.pgh.pa.us 819 [ - + ]:GNC 828 : if (uncommitted_enum_values)
21 tgl@sss.pgh.pa.us 820 :UNC 0 : entries += hash_get_num_entries(uncommitted_enum_values);
821 : :
822 : : /* Add two for the terminators. */
21 tgl@sss.pgh.pa.us 823 :GNC 828 : return sizeof(Oid) * (entries + 2);
824 : : }
825 : :
826 : : void
1195 tmunro@postgresql.or 827 :CBC 414 : SerializeUncommittedEnums(void *space, Size size)
828 : : {
2014 829 : 414 : 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 : : */
1195 835 [ - + ]: 414 : Assert(size == EstimateUncommittedEnumsSpace());
836 : :
837 : : /* Write out all the OIDs from the types hash table, if there is one. */
21 tgl@sss.pgh.pa.us 838 [ - + ]:GNC 414 : if (uncommitted_enum_types)
839 : : {
840 : : HASH_SEQ_STATUS status;
841 : : Oid *value;
842 : :
21 tgl@sss.pgh.pa.us 843 :UNC 0 : hash_seq_init(&status, uncommitted_enum_types);
2014 tmunro@postgresql.or 844 [ # # ]:UBC 0 : while ((value = (Oid *) hash_seq_search(&status)))
845 : 0 : *serialized++ = *value;
846 : : }
847 : :
848 : : /* Write out the terminator. */
21 tgl@sss.pgh.pa.us 849 :GNC 414 : *serialized++ = InvalidOid;
850 : :
851 : : /* Write out all the OIDs from the values hash table, if there is one. */
852 [ - + ]: 414 : if (uncommitted_enum_values)
853 : : {
854 : : HASH_SEQ_STATUS status;
855 : : Oid *value;
856 : :
21 tgl@sss.pgh.pa.us 857 :UNC 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. */
21 tgl@sss.pgh.pa.us 863 :GNC 414 : *serialized++ = InvalidOid;
864 : :
865 : : /*
866 : : * Make sure the amount of space we actually used matches what was
867 : : * estimated.
868 : : */
869 [ - + ]: 414 : Assert((char *) serialized == ((char *) space) + size);
2014 tmunro@postgresql.or 870 :CBC 414 : }
871 : :
872 : : void
1195 873 : 1322 : RestoreUncommittedEnums(void *space)
874 : : {
2014 875 : 1322 : Oid *serialized = (Oid *) space;
876 : :
21 tgl@sss.pgh.pa.us 877 [ - + ]:GNC 1322 : Assert(!uncommitted_enum_types);
878 [ - + ]: 1322 : 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 [ - + ]: 1322 : if (OidIsValid(*serialized))
886 : : {
887 : : /* Read all the types into a new hash table. */
21 tgl@sss.pgh.pa.us 888 :UNC 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 : : }
21 tgl@sss.pgh.pa.us 895 :GNC 1322 : serialized++;
896 [ - + ]: 1322 : if (OidIsValid(*serialized))
897 : : {
898 : : /* Read all the values into a new hash table. */
21 tgl@sss.pgh.pa.us 899 :UNC 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 : : }
2014 tmunro@postgresql.or 906 :GIC 1322 : }
|