1 /*-------------------------------------------------------------------------
4 * routines to support manipulation of the pg_enum relation
6 * Copyright (c) 2006-2011, PostgreSQL Global Development Group
10 * src/backend/catalog/pg_enum.c
12 *-------------------------------------------------------------------------
16 #include "access/genam.h"
17 #include "access/heapam.h"
18 #include "access/xact.h"
19 #include "catalog/catalog.h"
20 #include "catalog/indexing.h"
21 #include "catalog/pg_enum.h"
22 #include "catalog/pg_type.h"
23 #include "storage/lmgr.h"
24 #include "utils/builtins.h"
25 #include "utils/fmgroids.h"
26 #include "utils/rel.h"
27 #include "utils/syscache.h"
28 #include "utils/tqual.h"
31 Oid binary_upgrade_next_pg_enum_oid = InvalidOid;
33 static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
34 static int oid_cmp(const void *p1, const void *p2);
35 static int sort_order_cmp(const void *p1, const void *p2);
40 * Create an entry in pg_enum for each of the supplied enum values.
42 * vals is a list of Value strings.
45 EnumValuesCreate(Oid enumTypeOid, List *vals)
52 Datum values[Natts_pg_enum];
53 bool nulls[Natts_pg_enum];
57 num_elems = list_length(vals);
60 * We do not bother to check the list of values for duplicates --- if
61 * you have any, you'll get a less-than-friendly unique-index violation.
62 * It is probably not worth trying harder.
65 pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
68 * Allocate OIDs for the enum's members.
70 * While this method does not absolutely guarantee that we generate no
71 * duplicate OIDs (since we haven't entered each oid into the table
72 * before allocating the next), trouble could only occur if the OID
73 * counter wraps all the way around before we finish. Which seems
76 oids = (Oid *) palloc(num_elems * sizeof(Oid));
78 for (elemno = 0; elemno < num_elems; elemno++)
81 * We assign even-numbered OIDs to all the new enum labels. This
82 * tells the comparison functions the OIDs are in the correct sort
83 * order and can be compared directly.
88 new_oid = GetNewOid(pg_enum);
89 } while (new_oid & 1);
90 oids[elemno] = new_oid;
93 /* sort them, just in case OID counter wrapped from high to low */
94 qsort(oids, num_elems, sizeof(Oid), oid_cmp);
96 /* and make the entries */
97 memset(nulls, false, sizeof(nulls));
102 char *lab = strVal(lfirst(lc));
105 * labels are stored in a name field, for easier syscache lookup, so
106 * check the length to make sure it's within range.
108 if (strlen(lab) > (NAMEDATALEN - 1))
110 (errcode(ERRCODE_INVALID_NAME),
111 errmsg("invalid enum label \"%s\"", lab),
112 errdetail("Labels must be %d characters or less.",
115 values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
116 values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
117 namestrcpy(&enumlabel, lab);
118 values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
120 tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
121 HeapTupleSetOid(tup, oids[elemno]);
123 simple_heap_insert(pg_enum, tup);
124 CatalogUpdateIndexes(pg_enum, tup);
132 heap_close(pg_enum, RowExclusiveLock);
138 * Remove all the pg_enum entries for the specified enum type.
141 EnumValuesDelete(Oid enumTypeOid)
148 pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
151 Anum_pg_enum_enumtypid,
152 BTEqualStrategyNumber, F_OIDEQ,
153 ObjectIdGetDatum(enumTypeOid));
155 scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
156 SnapshotNow, 1, key);
158 while (HeapTupleIsValid(tup = systable_getnext(scan)))
160 simple_heap_delete(pg_enum, &tup->t_self);
163 systable_endscan(scan);
165 heap_close(pg_enum, RowExclusiveLock);
171 * Add a new label to the enum set. By default it goes at
172 * the end, but the user can choose to place it before or
173 * after any existing set member.
176 AddEnumLabel(Oid enumTypeOid,
178 const char *neighbor,
183 Datum values[Natts_pg_enum];
184 bool nulls[Natts_pg_enum];
193 /* check length of new label is ok */
194 if (strlen(newVal) > (NAMEDATALEN - 1))
196 (errcode(ERRCODE_INVALID_NAME),
197 errmsg("invalid enum label \"%s\"", newVal),
198 errdetail("Labels must be %d characters or less.",
202 * Acquire a lock on the enum type, which we won't release until commit.
203 * This ensures that two backends aren't concurrently modifying the same
204 * enum type. Without that, we couldn't be sure to get a consistent
205 * view of the enum members via the syscache. Note that this does not
206 * block other backends from inspecting the type; see comments for
209 LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
211 pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
213 /* If we have to renumber the existing members, we restart from here */
216 /* Get the list of existing members of the enum */
217 list = SearchSysCacheList1(ENUMTYPOIDNAME,
218 ObjectIdGetDatum(enumTypeOid));
219 nelems = list->n_members;
221 /* Sort the existing members by enumsortorder */
222 existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
223 for (i = 0; i < nelems; i++)
224 existing[i] = &(list->members[i]->tuple);
226 qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
228 if (neighbor == NULL)
231 * Put the new label at the end of the list.
232 * No change to existing tuples is required.
236 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
238 newelemorder = en->enumsortorder + 1;
245 /* BEFORE or AFTER was specified */
249 Form_pg_enum other_nbr_en;
251 /* Locate the neighbor element */
252 for (nbr_index = 0; nbr_index < nelems; nbr_index++)
254 Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
256 if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
259 if (nbr_index >= nelems)
261 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
262 errmsg("\"%s\" is not an existing enum label",
264 nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
267 * Attempt to assign an appropriate enumsortorder value: one less
268 * than the smallest member, one more than the largest member,
269 * or halfway between two existing members.
271 * In the "halfway" case, because of the finite precision of float4,
272 * we might compute a value that's actually equal to one or the
273 * other of its neighbors. In that case we renumber the existing
274 * members and try again.
277 other_nbr_index = nbr_index + 1;
279 other_nbr_index = nbr_index - 1;
281 if (other_nbr_index < 0)
282 newelemorder = nbr_en->enumsortorder - 1;
283 else if (other_nbr_index >= nelems)
284 newelemorder = nbr_en->enumsortorder + 1;
287 other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
288 newelemorder = (nbr_en->enumsortorder +
289 other_nbr_en->enumsortorder) / 2;
292 * On some machines, newelemorder may be in a register that's
293 * wider than float4. We need to force it to be rounded to
294 * float4 precision before making the following comparisons,
295 * or we'll get wrong results. (Such behavior violates the C
296 * standard, but fixing the compilers is out of our reach.)
298 newelemorder = DatumGetFloat4(Float4GetDatum(newelemorder));
300 if (newelemorder == nbr_en->enumsortorder ||
301 newelemorder == other_nbr_en->enumsortorder)
303 RenumberEnumType(pg_enum, existing, nelems);
304 /* Clean up and start over */
306 ReleaseCatCacheList(list);
312 /* Get a new OID for the new label */
313 if (OidIsValid(binary_upgrade_next_pg_enum_oid))
316 * In binary upgrades, just add the new label with the predetermined
317 * Oid. It's pg_upgrade's responsibility that the Oid meets
320 if (neighbor != NULL)
322 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
323 errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
325 newOid = binary_upgrade_next_pg_enum_oid;
326 binary_upgrade_next_pg_enum_oid = InvalidOid;
331 * Normal case: we need to allocate a new Oid for the value.
333 * We want to give the new element an even-numbered Oid if it's safe,
334 * which is to say it compares correctly to all pre-existing even
335 * numbered Oids in the enum. Otherwise, we must give it an odd Oid.
341 /* Get a new OID (different from all existing pg_enum tuples) */
342 newOid = GetNewOid(pg_enum);
345 * Detect whether it sorts correctly relative to existing
346 * even-numbered labels of the enum. We can ignore existing
347 * labels with odd Oids, since a comparison involving one of
348 * those will not take the fast path anyway.
351 for (i = 0; i < nelems; i++)
353 HeapTuple exists_tup = existing[i];
354 Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
355 Oid exists_oid = HeapTupleGetOid(exists_tup);
358 continue; /* ignore odd Oids */
360 if (exists_en->enumsortorder < newelemorder)
362 /* should sort before */
363 if (exists_oid >= newOid)
371 /* should sort after */
372 if (exists_oid <= newOid)
382 /* If it's even and sorts OK, we're done. */
383 if ((newOid & 1) == 0)
387 * If it's odd, and sorts OK, loop back to get another OID
388 * and try again. Probably, the next available even OID
389 * will sort correctly too, so it's worth trying.
395 * If it's odd, and does not sort correctly, we're done.
396 * (Probably, the next available even OID would sort
397 * incorrectly too, so no point in trying again.)
403 * If it's even, and does not sort correctly, loop back to get
404 * another OID and try again. (We *must* reject this case.)
410 /* Done with info about existing members */
412 ReleaseCatCacheList(list);
414 /* Create the new pg_enum entry */
415 memset(nulls, false, sizeof(nulls));
416 values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
417 values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
418 namestrcpy(&enumlabel, newVal);
419 values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
420 enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
421 HeapTupleSetOid(enum_tup, newOid);
422 simple_heap_insert(pg_enum, enum_tup);
423 CatalogUpdateIndexes(pg_enum, enum_tup);
424 heap_freetuple(enum_tup);
426 heap_close(pg_enum, RowExclusiveLock);
432 * Renumber existing enum elements to have sort positions 1..n.
434 * We avoid doing this unless absolutely necessary; in most installations
435 * it will never happen. The reason is that updating existing pg_enum
436 * entries creates hazards for other backends that are concurrently reading
437 * pg_enum with SnapshotNow semantics. A concurrent SnapshotNow scan could
438 * see both old and new versions of an updated row as valid, or neither of
439 * them, if the commit happens between scanning the two versions. It's
440 * also quite likely for a concurrent scan to see an inconsistent set of
441 * rows (some members updated, some not).
443 * We can avoid these risks by reading pg_enum with an MVCC snapshot
444 * instead of SnapshotNow, but that forecloses use of the syscaches.
445 * We therefore make the following choices:
447 * 1. Any code that is interested in the enumsortorder values MUST read
448 * pg_enum with an MVCC snapshot, or else acquire lock on the enum type
449 * to prevent concurrent execution of AddEnumLabel(). The risk of
450 * seeing inconsistent values of enumsortorder is too high otherwise.
452 * 2. Code that is not examining enumsortorder can use a syscache
453 * (for example, enum_in and enum_out do so). The worst that can happen
454 * is a transient failure to find any valid value of the row. This is
455 * judged acceptable in view of the infrequency of use of RenumberEnumType.
458 RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
463 * We should only need to increase existing elements' enumsortorders,
464 * never decrease them. Therefore, work from the end backwards, to avoid
465 * unwanted uniqueness violations.
467 for (i = nelems - 1; i >= 0; i--)
473 newtup = heap_copytuple(existing[i]);
474 en = (Form_pg_enum) GETSTRUCT(newtup);
476 newsortorder = i + 1;
477 if (en->enumsortorder != newsortorder)
479 en->enumsortorder = newsortorder;
481 simple_heap_update(pg_enum, &newtup->t_self, newtup);
483 CatalogUpdateIndexes(pg_enum, newtup);
486 heap_freetuple(newtup);
489 /* Make the updates visible */
490 CommandCounterIncrement();
494 /* qsort comparison function for oids */
496 oid_cmp(const void *p1, const void *p2)
498 Oid v1 = *((const Oid *) p1);
499 Oid v2 = *((const Oid *) p2);
508 /* qsort comparison function for tuples by sort order */
510 sort_order_cmp(const void *p1, const void *p2)
512 HeapTuple v1 = *((const HeapTuple *) p1);
513 HeapTuple v2 = *((const HeapTuple *) p2);
514 Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
515 Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
517 if (en1->enumsortorder < en2->enumsortorder)
519 else if (en1->enumsortorder > en2->enumsortorder)