Header And Logo

PostgreSQL
| The world's most advanced open source database.

pg_enum.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * pg_enum.c
00004  *    routines to support manipulation of the pg_enum relation
00005  *
00006  * Copyright (c) 2006-2013, PostgreSQL Global Development Group
00007  *
00008  *
00009  * IDENTIFICATION
00010  *    src/backend/catalog/pg_enum.c
00011  *
00012  *-------------------------------------------------------------------------
00013  */
00014 #include "postgres.h"
00015 
00016 #include "access/genam.h"
00017 #include "access/heapam.h"
00018 #include "access/htup_details.h"
00019 #include "access/xact.h"
00020 #include "catalog/catalog.h"
00021 #include "catalog/indexing.h"
00022 #include "catalog/pg_enum.h"
00023 #include "catalog/pg_type.h"
00024 #include "storage/lmgr.h"
00025 #include "miscadmin.h"
00026 #include "utils/builtins.h"
00027 #include "utils/catcache.h"
00028 #include "utils/fmgroids.h"
00029 #include "utils/syscache.h"
00030 #include "utils/tqual.h"
00031 
00032 
00033 /* Potentially set by contrib/pg_upgrade_support functions */
00034 Oid         binary_upgrade_next_pg_enum_oid = InvalidOid;
00035 
00036 static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
00037 static int  oid_cmp(const void *p1, const void *p2);
00038 static int  sort_order_cmp(const void *p1, const void *p2);
00039 
00040 
00041 /*
00042  * EnumValuesCreate
00043  *      Create an entry in pg_enum for each of the supplied enum values.
00044  *
00045  * vals is a list of Value strings.
00046  */
00047 void
00048 EnumValuesCreate(Oid enumTypeOid, List *vals)
00049 {
00050     Relation    pg_enum;
00051     NameData    enumlabel;
00052     Oid        *oids;
00053     int         elemno,
00054                 num_elems;
00055     Datum       values[Natts_pg_enum];
00056     bool        nulls[Natts_pg_enum];
00057     ListCell   *lc;
00058     HeapTuple   tup;
00059 
00060     num_elems = list_length(vals);
00061 
00062     /*
00063      * We do not bother to check the list of values for duplicates --- if you
00064      * have any, you'll get a less-than-friendly unique-index violation. It is
00065      * probably not worth trying harder.
00066      */
00067 
00068     pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
00069 
00070     /*
00071      * Allocate OIDs for the enum's members.
00072      *
00073      * While this method does not absolutely guarantee that we generate no
00074      * duplicate OIDs (since we haven't entered each oid into the table before
00075      * allocating the next), trouble could only occur if the OID counter wraps
00076      * all the way around before we finish. Which seems unlikely.
00077      */
00078     oids = (Oid *) palloc(num_elems * sizeof(Oid));
00079 
00080     for (elemno = 0; elemno < num_elems; elemno++)
00081     {
00082         /*
00083          * We assign even-numbered OIDs to all the new enum labels.  This
00084          * tells the comparison functions the OIDs are in the correct sort
00085          * order and can be compared directly.
00086          */
00087         Oid         new_oid;
00088 
00089         do
00090         {
00091             new_oid = GetNewOid(pg_enum);
00092         } while (new_oid & 1);
00093         oids[elemno] = new_oid;
00094     }
00095 
00096     /* sort them, just in case OID counter wrapped from high to low */
00097     qsort(oids, num_elems, sizeof(Oid), oid_cmp);
00098 
00099     /* and make the entries */
00100     memset(nulls, false, sizeof(nulls));
00101 
00102     elemno = 0;
00103     foreach(lc, vals)
00104     {
00105         char       *lab = strVal(lfirst(lc));
00106 
00107         /*
00108          * labels are stored in a name field, for easier syscache lookup, so
00109          * check the length to make sure it's within range.
00110          */
00111         if (strlen(lab) > (NAMEDATALEN - 1))
00112             ereport(ERROR,
00113                     (errcode(ERRCODE_INVALID_NAME),
00114                      errmsg("invalid enum label \"%s\"", lab),
00115                      errdetail("Labels must be %d characters or less.",
00116                                NAMEDATALEN - 1)));
00117 
00118         values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
00119         values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
00120         namestrcpy(&enumlabel, lab);
00121         values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
00122 
00123         tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
00124         HeapTupleSetOid(tup, oids[elemno]);
00125 
00126         simple_heap_insert(pg_enum, tup);
00127         CatalogUpdateIndexes(pg_enum, tup);
00128         heap_freetuple(tup);
00129 
00130         elemno++;
00131     }
00132 
00133     /* clean up */
00134     pfree(oids);
00135     heap_close(pg_enum, RowExclusiveLock);
00136 }
00137 
00138 
00139 /*
00140  * EnumValuesDelete
00141  *      Remove all the pg_enum entries for the specified enum type.
00142  */
00143 void
00144 EnumValuesDelete(Oid enumTypeOid)
00145 {
00146     Relation    pg_enum;
00147     ScanKeyData key[1];
00148     SysScanDesc scan;
00149     HeapTuple   tup;
00150 
00151     pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
00152 
00153     ScanKeyInit(&key[0],
00154                 Anum_pg_enum_enumtypid,
00155                 BTEqualStrategyNumber, F_OIDEQ,
00156                 ObjectIdGetDatum(enumTypeOid));
00157 
00158     scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
00159                               SnapshotNow, 1, key);
00160 
00161     while (HeapTupleIsValid(tup = systable_getnext(scan)))
00162     {
00163         simple_heap_delete(pg_enum, &tup->t_self);
00164     }
00165 
00166     systable_endscan(scan);
00167 
00168     heap_close(pg_enum, RowExclusiveLock);
00169 }
00170 
00171 
00172 /*
00173  * AddEnumLabel
00174  *      Add a new label to the enum set. By default it goes at
00175  *      the end, but the user can choose to place it before or
00176  *      after any existing set member.
00177  */
00178 void
00179 AddEnumLabel(Oid enumTypeOid,
00180              const char *newVal,
00181              const char *neighbor,
00182              bool newValIsAfter,
00183              bool skipIfExists)
00184 {
00185     Relation    pg_enum;
00186     Oid         newOid;
00187     Datum       values[Natts_pg_enum];
00188     bool        nulls[Natts_pg_enum];
00189     NameData    enumlabel;
00190     HeapTuple   enum_tup;
00191     float4      newelemorder;
00192     HeapTuple  *existing;
00193     CatCList   *list;
00194     int         nelems;
00195     int         i;
00196 
00197     /* check length of new label is ok */
00198     if (strlen(newVal) > (NAMEDATALEN - 1))
00199         ereport(ERROR,
00200                 (errcode(ERRCODE_INVALID_NAME),
00201                  errmsg("invalid enum label \"%s\"", newVal),
00202                  errdetail("Labels must be %d characters or less.",
00203                            NAMEDATALEN - 1)));
00204 
00205     /*
00206      * Acquire a lock on the enum type, which we won't release until commit.
00207      * This ensures that two backends aren't concurrently modifying the same
00208      * enum type.  Without that, we couldn't be sure to get a consistent view
00209      * of the enum members via the syscache.  Note that this does not block
00210      * other backends from inspecting the type; see comments for
00211      * RenumberEnumType.
00212      */
00213     LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
00214 
00215     /*
00216      * Check if label is already in use.  The unique index on pg_enum would
00217      * catch this anyway, but we prefer a friendlier error message, and
00218      * besides we need a check to support IF NOT EXISTS.
00219      */
00220     enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
00221                                ObjectIdGetDatum(enumTypeOid),
00222                                CStringGetDatum(newVal));
00223     if (HeapTupleIsValid(enum_tup))
00224     {
00225         ReleaseSysCache(enum_tup);
00226         if (skipIfExists)
00227         {
00228             ereport(NOTICE,
00229                     (errcode(ERRCODE_DUPLICATE_OBJECT),
00230                      errmsg("enum label \"%s\" already exists, skipping",
00231                             newVal)));
00232             return;
00233         }
00234         else
00235             ereport(ERROR,
00236                     (errcode(ERRCODE_DUPLICATE_OBJECT),
00237                      errmsg("enum label \"%s\" already exists",
00238                             newVal)));
00239     }
00240 
00241     pg_enum = heap_open(EnumRelationId, RowExclusiveLock);
00242 
00243     /* If we have to renumber the existing members, we restart from here */
00244 restart:
00245 
00246     /* Get the list of existing members of the enum */
00247     list = SearchSysCacheList1(ENUMTYPOIDNAME,
00248                                ObjectIdGetDatum(enumTypeOid));
00249     nelems = list->n_members;
00250 
00251     /* Sort the existing members by enumsortorder */
00252     existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
00253     for (i = 0; i < nelems; i++)
00254         existing[i] = &(list->members[i]->tuple);
00255 
00256     qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
00257 
00258     if (neighbor == NULL)
00259     {
00260         /*
00261          * Put the new label at the end of the list. No change to existing
00262          * tuples is required.
00263          */
00264         if (nelems > 0)
00265         {
00266             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
00267 
00268             newelemorder = en->enumsortorder + 1;
00269         }
00270         else
00271             newelemorder = 1;
00272     }
00273     else
00274     {
00275         /* BEFORE or AFTER was specified */
00276         int         nbr_index;
00277         int         other_nbr_index;
00278         Form_pg_enum nbr_en;
00279         Form_pg_enum other_nbr_en;
00280 
00281         /* Locate the neighbor element */
00282         for (nbr_index = 0; nbr_index < nelems; nbr_index++)
00283         {
00284             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
00285 
00286             if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
00287                 break;
00288         }
00289         if (nbr_index >= nelems)
00290             ereport(ERROR,
00291                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00292                      errmsg("\"%s\" is not an existing enum label",
00293                             neighbor)));
00294         nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
00295 
00296         /*
00297          * Attempt to assign an appropriate enumsortorder value: one less than
00298          * the smallest member, one more than the largest member, or halfway
00299          * between two existing members.
00300          *
00301          * In the "halfway" case, because of the finite precision of float4,
00302          * we might compute a value that's actually equal to one or the other
00303          * of its neighbors.  In that case we renumber the existing members
00304          * and try again.
00305          */
00306         if (newValIsAfter)
00307             other_nbr_index = nbr_index + 1;
00308         else
00309             other_nbr_index = nbr_index - 1;
00310 
00311         if (other_nbr_index < 0)
00312             newelemorder = nbr_en->enumsortorder - 1;
00313         else if (other_nbr_index >= nelems)
00314             newelemorder = nbr_en->enumsortorder + 1;
00315         else
00316         {
00317             other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
00318             newelemorder = (nbr_en->enumsortorder +
00319                             other_nbr_en->enumsortorder) / 2;
00320 
00321             /*
00322              * On some machines, newelemorder may be in a register that's
00323              * wider than float4.  We need to force it to be rounded to float4
00324              * precision before making the following comparisons, or we'll get
00325              * wrong results.  (Such behavior violates the C standard, but
00326              * fixing the compilers is out of our reach.)
00327              */
00328             newelemorder = DatumGetFloat4(Float4GetDatum(newelemorder));
00329 
00330             if (newelemorder == nbr_en->enumsortorder ||
00331                 newelemorder == other_nbr_en->enumsortorder)
00332             {
00333                 RenumberEnumType(pg_enum, existing, nelems);
00334                 /* Clean up and start over */
00335                 pfree(existing);
00336                 ReleaseCatCacheList(list);
00337                 goto restart;
00338             }
00339         }
00340     }
00341 
00342     /* Get a new OID for the new label */
00343     if (IsBinaryUpgrade && OidIsValid(binary_upgrade_next_pg_enum_oid))
00344     {
00345         /*
00346          * Use binary-upgrade override for pg_enum.oid, if supplied. During
00347          * binary upgrade, all pg_enum.oid's are set this way so they are
00348          * guaranteed to be consistent.
00349          */
00350         if (neighbor != NULL)
00351             ereport(ERROR,
00352                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00353                      errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
00354 
00355         newOid = binary_upgrade_next_pg_enum_oid;
00356         binary_upgrade_next_pg_enum_oid = InvalidOid;
00357     }
00358     else
00359     {
00360         /*
00361          * Normal case: we need to allocate a new Oid for the value.
00362          *
00363          * We want to give the new element an even-numbered Oid if it's safe,
00364          * which is to say it compares correctly to all pre-existing even
00365          * numbered Oids in the enum.  Otherwise, we must give it an odd Oid.
00366          */
00367         for (;;)
00368         {
00369             bool        sorts_ok;
00370 
00371             /* Get a new OID (different from all existing pg_enum tuples) */
00372             newOid = GetNewOid(pg_enum);
00373 
00374             /*
00375              * Detect whether it sorts correctly relative to existing
00376              * even-numbered labels of the enum.  We can ignore existing
00377              * labels with odd Oids, since a comparison involving one of those
00378              * will not take the fast path anyway.
00379              */
00380             sorts_ok = true;
00381             for (i = 0; i < nelems; i++)
00382             {
00383                 HeapTuple   exists_tup = existing[i];
00384                 Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
00385                 Oid         exists_oid = HeapTupleGetOid(exists_tup);
00386 
00387                 if (exists_oid & 1)
00388                     continue;   /* ignore odd Oids */
00389 
00390                 if (exists_en->enumsortorder < newelemorder)
00391                 {
00392                     /* should sort before */
00393                     if (exists_oid >= newOid)
00394                     {
00395                         sorts_ok = false;
00396                         break;
00397                     }
00398                 }
00399                 else
00400                 {
00401                     /* should sort after */
00402                     if (exists_oid <= newOid)
00403                     {
00404                         sorts_ok = false;
00405                         break;
00406                     }
00407                 }
00408             }
00409 
00410             if (sorts_ok)
00411             {
00412                 /* If it's even and sorts OK, we're done. */
00413                 if ((newOid & 1) == 0)
00414                     break;
00415 
00416                 /*
00417                  * If it's odd, and sorts OK, loop back to get another OID and
00418                  * try again.  Probably, the next available even OID will sort
00419                  * correctly too, so it's worth trying.
00420                  */
00421             }
00422             else
00423             {
00424                 /*
00425                  * If it's odd, and does not sort correctly, we're done.
00426                  * (Probably, the next available even OID would sort
00427                  * incorrectly too, so no point in trying again.)
00428                  */
00429                 if (newOid & 1)
00430                     break;
00431 
00432                 /*
00433                  * If it's even, and does not sort correctly, loop back to get
00434                  * another OID and try again.  (We *must* reject this case.)
00435                  */
00436             }
00437         }
00438     }
00439 
00440     /* Done with info about existing members */
00441     pfree(existing);
00442     ReleaseCatCacheList(list);
00443 
00444     /* Create the new pg_enum entry */
00445     memset(nulls, false, sizeof(nulls));
00446     values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
00447     values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
00448     namestrcpy(&enumlabel, newVal);
00449     values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
00450     enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
00451     HeapTupleSetOid(enum_tup, newOid);
00452     simple_heap_insert(pg_enum, enum_tup);
00453     CatalogUpdateIndexes(pg_enum, enum_tup);
00454     heap_freetuple(enum_tup);
00455 
00456     heap_close(pg_enum, RowExclusiveLock);
00457 }
00458 
00459 
00460 /*
00461  * RenumberEnumType
00462  *      Renumber existing enum elements to have sort positions 1..n.
00463  *
00464  * We avoid doing this unless absolutely necessary; in most installations
00465  * it will never happen.  The reason is that updating existing pg_enum
00466  * entries creates hazards for other backends that are concurrently reading
00467  * pg_enum with SnapshotNow semantics.  A concurrent SnapshotNow scan could
00468  * see both old and new versions of an updated row as valid, or neither of
00469  * them, if the commit happens between scanning the two versions.  It's
00470  * also quite likely for a concurrent scan to see an inconsistent set of
00471  * rows (some members updated, some not).
00472  *
00473  * We can avoid these risks by reading pg_enum with an MVCC snapshot
00474  * instead of SnapshotNow, but that forecloses use of the syscaches.
00475  * We therefore make the following choices:
00476  *
00477  * 1. Any code that is interested in the enumsortorder values MUST read
00478  * pg_enum with an MVCC snapshot, or else acquire lock on the enum type
00479  * to prevent concurrent execution of AddEnumLabel().  The risk of
00480  * seeing inconsistent values of enumsortorder is too high otherwise.
00481  *
00482  * 2. Code that is not examining enumsortorder can use a syscache
00483  * (for example, enum_in and enum_out do so).  The worst that can happen
00484  * is a transient failure to find any valid value of the row.  This is
00485  * judged acceptable in view of the infrequency of use of RenumberEnumType.
00486  */
00487 static void
00488 RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
00489 {
00490     int         i;
00491 
00492     /*
00493      * We should only need to increase existing elements' enumsortorders,
00494      * never decrease them.  Therefore, work from the end backwards, to avoid
00495      * unwanted uniqueness violations.
00496      */
00497     for (i = nelems - 1; i >= 0; i--)
00498     {
00499         HeapTuple   newtup;
00500         Form_pg_enum en;
00501         float4      newsortorder;
00502 
00503         newtup = heap_copytuple(existing[i]);
00504         en = (Form_pg_enum) GETSTRUCT(newtup);
00505 
00506         newsortorder = i + 1;
00507         if (en->enumsortorder != newsortorder)
00508         {
00509             en->enumsortorder = newsortorder;
00510 
00511             simple_heap_update(pg_enum, &newtup->t_self, newtup);
00512 
00513             CatalogUpdateIndexes(pg_enum, newtup);
00514         }
00515 
00516         heap_freetuple(newtup);
00517     }
00518 
00519     /* Make the updates visible */
00520     CommandCounterIncrement();
00521 }
00522 
00523 
00524 /* qsort comparison function for oids */
00525 static int
00526 oid_cmp(const void *p1, const void *p2)
00527 {
00528     Oid         v1 = *((const Oid *) p1);
00529     Oid         v2 = *((const Oid *) p2);
00530 
00531     if (v1 < v2)
00532         return -1;
00533     if (v1 > v2)
00534         return 1;
00535     return 0;
00536 }
00537 
00538 /* qsort comparison function for tuples by sort order */
00539 static int
00540 sort_order_cmp(const void *p1, const void *p2)
00541 {
00542     HeapTuple   v1 = *((const HeapTuple *) p1);
00543     HeapTuple   v2 = *((const HeapTuple *) p2);
00544     Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
00545     Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
00546 
00547     if (en1->enumsortorder < en2->enumsortorder)
00548         return -1;
00549     else if (en1->enumsortorder > en2->enumsortorder)
00550         return 1;
00551     else
00552         return 0;
00553 }