]> granicus.if.org Git - postgresql/blob - src/backend/catalog/pg_enum.c
0cc0a0c1b386c72116ac593e7c778994c5f7f583
[postgresql] / src / backend / catalog / pg_enum.c
1 /*-------------------------------------------------------------------------
2  *
3  * pg_enum.c
4  *        routines to support manipulation of the pg_enum relation
5  *
6  * Copyright (c) 2006-2011, 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/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"
29
30
31 Oid      binary_upgrade_next_pg_enum_oid = InvalidOid;
32
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);
36
37
38 /*
39  * EnumValuesCreate
40  *              Create an entry in pg_enum for each of the supplied enum values.
41  *
42  * vals is a list of Value strings.
43  */
44 void
45 EnumValuesCreate(Oid enumTypeOid, List *vals)
46 {
47         Relation        pg_enum;
48         NameData        enumlabel;
49         Oid                *oids;
50         int                     elemno,
51                                 num_elems;
52         Datum           values[Natts_pg_enum];
53         bool            nulls[Natts_pg_enum];
54         ListCell   *lc;
55         HeapTuple       tup;
56
57         num_elems = list_length(vals);
58
59         /*
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.
63          */
64
65         pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
66
67         /*
68          * Allocate OIDs for the enum's members.
69          *
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
74          * unlikely.
75          */
76         oids = (Oid *) palloc(num_elems * sizeof(Oid));
77
78         for (elemno = 0; elemno < num_elems; elemno++)
79         {
80                 /*
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.
84                  */
85                 Oid             new_oid;
86
87                 do {
88                         new_oid = GetNewOid(pg_enum);
89                 } while (new_oid & 1);
90                 oids[elemno] = new_oid;
91         }
92
93         /* sort them, just in case OID counter wrapped from high to low */
94         qsort(oids, num_elems, sizeof(Oid), oid_cmp);
95
96         /* and make the entries */
97         memset(nulls, false, sizeof(nulls));
98
99         elemno = 0;
100         foreach(lc, vals)
101         {
102                 char       *lab = strVal(lfirst(lc));
103
104                 /*
105                  * labels are stored in a name field, for easier syscache lookup, so
106                  * check the length to make sure it's within range.
107                  */
108                 if (strlen(lab) > (NAMEDATALEN - 1))
109                         ereport(ERROR,
110                                         (errcode(ERRCODE_INVALID_NAME),
111                                          errmsg("invalid enum label \"%s\"", lab),
112                                          errdetail("Labels must be %d characters or less.",
113                                                            NAMEDATALEN - 1)));
114
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);
119
120                 tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
121                 HeapTupleSetOid(tup, oids[elemno]);
122
123                 simple_heap_insert(pg_enum, tup);
124                 CatalogUpdateIndexes(pg_enum, tup);
125                 heap_freetuple(tup);
126
127                 elemno++;
128         }
129
130         /* clean up */
131         pfree(oids);
132         heap_close(pg_enum, RowExclusiveLock);
133 }
134
135
136 /*
137  * EnumValuesDelete
138  *              Remove all the pg_enum entries for the specified enum type.
139  */
140 void
141 EnumValuesDelete(Oid enumTypeOid)
142 {
143         Relation        pg_enum;
144         ScanKeyData key[1];
145         SysScanDesc scan;
146         HeapTuple       tup;
147
148         pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
149
150         ScanKeyInit(&key[0],
151                                 Anum_pg_enum_enumtypid,
152                                 BTEqualStrategyNumber, F_OIDEQ,
153                                 ObjectIdGetDatum(enumTypeOid));
154
155         scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
156                                                           SnapshotNow, 1, key);
157
158         while (HeapTupleIsValid(tup = systable_getnext(scan)))
159         {
160                 simple_heap_delete(pg_enum, &tup->t_self);
161         }
162
163         systable_endscan(scan);
164
165         heap_close(pg_enum, RowExclusiveLock);
166 }
167
168
169 /*
170  * AddEnumLabel
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.
174  */
175 void
176 AddEnumLabel(Oid enumTypeOid,
177                          const char *newVal,
178                          const char *neighbor,
179                          bool newValIsAfter)
180 {
181         Relation        pg_enum;
182         Oid                     newOid;
183         Datum           values[Natts_pg_enum];
184         bool            nulls[Natts_pg_enum];
185         NameData        enumlabel;
186         HeapTuple       enum_tup;
187         float4          newelemorder;
188         HeapTuple  *existing;
189         CatCList   *list;
190         int                     nelems;
191         int                     i;
192
193         /* check length of new label is ok */
194         if (strlen(newVal) > (NAMEDATALEN - 1))
195                 ereport(ERROR,
196                                 (errcode(ERRCODE_INVALID_NAME),
197                                  errmsg("invalid enum label \"%s\"", newVal),
198                                  errdetail("Labels must be %d characters or less.",
199                                                    NAMEDATALEN - 1)));
200
201         /*
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
207          * RenumberEnumType.
208          */
209         LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
210
211         pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
212
213         /* If we have to renumber the existing members, we restart from here */
214 restart:
215
216         /* Get the list of existing members of the enum */
217         list = SearchSysCacheList1(ENUMTYPOIDNAME,
218                                                            ObjectIdGetDatum(enumTypeOid));
219         nelems =  list->n_members;
220
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);
225
226         qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
227
228         if (neighbor == NULL)
229         {
230                 /*
231                  * Put the new label at the end of the list.
232                  * No change to existing tuples is required.
233                  */
234                 if (nelems > 0)
235                 {
236                         Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
237
238                         newelemorder = en->enumsortorder + 1;
239                 }
240                 else
241                         newelemorder = 1;
242         }
243         else
244         {
245                 /* BEFORE or AFTER was specified */
246                 int                             nbr_index;
247                 int                             other_nbr_index;
248                 Form_pg_enum    nbr_en;
249                 Form_pg_enum    other_nbr_en;
250
251                 /* Locate the neighbor element */
252                 for (nbr_index = 0; nbr_index < nelems; nbr_index++)
253                 {
254                         Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
255
256                         if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
257                                 break;
258                 }
259                 if (nbr_index >= nelems)
260                         ereport(ERROR,
261                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
262                                          errmsg("\"%s\" is not an existing enum label",
263                                                         neighbor)));
264                 nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
265
266                 /*
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.
270                  *
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.
275                  */
276                 if (newValIsAfter)
277                         other_nbr_index = nbr_index + 1;
278                 else
279                         other_nbr_index = nbr_index - 1;
280
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;
285                 else
286                 {
287                         other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
288                         newelemorder = (nbr_en->enumsortorder +
289                                                         other_nbr_en->enumsortorder) / 2;
290
291                         /*
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.)
297                          */
298                         newelemorder = DatumGetFloat4(Float4GetDatum(newelemorder));
299
300                         if (newelemorder == nbr_en->enumsortorder ||
301                                 newelemorder == other_nbr_en->enumsortorder)
302                         {
303                                 RenumberEnumType(pg_enum, existing, nelems);
304                                 /* Clean up and start over */
305                                 pfree(existing);
306                                 ReleaseCatCacheList(list);
307                                 goto restart;
308                         }
309                 }
310         }
311
312         /* Get a new OID for the new label */
313         if (OidIsValid(binary_upgrade_next_pg_enum_oid))
314         {
315                 /*
316                  * In binary upgrades, just add the new label with the predetermined
317                  * Oid.  It's pg_upgrade's responsibility that the Oid meets
318                  * requirements.
319                  */
320                 if (neighbor != NULL)
321                         ereport(ERROR,
322                                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
323                                          errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
324
325                 newOid = binary_upgrade_next_pg_enum_oid;
326                 binary_upgrade_next_pg_enum_oid = InvalidOid;
327         }
328         else
329         {
330                 /*
331                  * Normal case: we need to allocate a new Oid for the value.
332                  *
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.
336                  */
337                 for (;;)
338                 {
339                         bool    sorts_ok;
340
341                         /* Get a new OID (different from all existing pg_enum tuples) */
342                         newOid = GetNewOid(pg_enum);
343
344                         /*
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.
349                          */
350                         sorts_ok = true;
351                         for (i = 0; i < nelems; i++)
352                         {
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);
356
357                                 if (exists_oid & 1)
358                                         continue;       /* ignore odd Oids */
359
360                                 if (exists_en->enumsortorder < newelemorder)
361                                 {
362                                         /* should sort before */
363                                         if (exists_oid >= newOid)
364                                         {
365                                                 sorts_ok = false;
366                                                 break;
367                                         }
368                                 }
369                                 else
370                                 {
371                                         /* should sort after */
372                                         if (exists_oid <= newOid)
373                                         {
374                                                 sorts_ok = false;
375                                                 break;
376                                         }
377                                 }
378                         }
379
380                         if (sorts_ok)
381                         {
382                                 /* If it's even and sorts OK, we're done. */
383                                 if ((newOid & 1) == 0)
384                                         break;
385
386                                 /*
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.
390                                  */
391                         }
392                         else
393                         {
394                                 /*
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.)
398                                  */
399                                 if (newOid & 1)
400                                         break;
401
402                                 /*
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.)
405                                  */
406                         }
407                 }
408         }
409
410         /* Done with info about existing members */
411         pfree(existing);
412         ReleaseCatCacheList(list);
413
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);
425
426         heap_close(pg_enum, RowExclusiveLock);
427 }
428
429
430 /*
431  * RenumberEnumType
432  *              Renumber existing enum elements to have sort positions 1..n.
433  *
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).
442  *
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:
446  *
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.
451  *
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.
456  */
457 static void
458 RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
459 {
460         int                     i;
461
462         /*
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.
466          */
467         for (i = nelems - 1; i >= 0; i--)
468         {
469                 HeapTuple       newtup;
470                 Form_pg_enum en;
471                 float4          newsortorder;
472
473                 newtup = heap_copytuple(existing[i]);
474                 en = (Form_pg_enum) GETSTRUCT(newtup);
475
476                 newsortorder = i + 1;
477                 if (en->enumsortorder != newsortorder)
478                 {
479                         en->enumsortorder = newsortorder;
480
481                         simple_heap_update(pg_enum, &newtup->t_self, newtup);
482
483                         CatalogUpdateIndexes(pg_enum, newtup);
484                 }
485
486                 heap_freetuple(newtup);
487         }
488
489         /* Make the updates visible */
490         CommandCounterIncrement();
491 }
492
493
494 /* qsort comparison function for oids */
495 static int
496 oid_cmp(const void *p1, const void *p2)
497 {
498         Oid                     v1 = *((const Oid *) p1);
499         Oid                     v2 = *((const Oid *) p2);
500
501         if (v1 < v2)
502                 return -1;
503         if (v1 > v2)
504                 return 1;
505         return 0;
506 }
507
508 /* qsort comparison function for tuples by sort order */
509 static int
510 sort_order_cmp(const void *p1, const void *p2)
511 {
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);
516
517         if (en1->enumsortorder < en2->enumsortorder)
518                 return -1;
519         else if (en1->enumsortorder > en2->enumsortorder)
520                 return 1;
521         else
522                 return 0;
523 }