LCOV - code coverage report
Current view: top level - src/backend/catalog - pg_enum.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13devel Lines: 196 210 93.3 %
Date: 2019-09-22 08:06:49 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          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-2019, 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 "storage/lmgr.h"
      26             : #include "miscadmin.h"
      27             : #include "nodes/value.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             : 
      36             : /* Potentially set by pg_upgrade_support functions */
      37             : Oid         binary_upgrade_next_pg_enum_oid = InvalidOid;
      38             : 
      39             : /*
      40             :  * Hash table of enum value OIDs created during the current transaction by
      41             :  * AddEnumLabel.  We disallow using these values until the transaction is
      42             :  * committed; otherwise, they might get into indexes where we can't clean
      43             :  * them up, and then if the transaction rolls back we have a broken index.
      44             :  * (See comments for check_safe_enum_use() in enum.c.)  Values created by
      45             :  * EnumValuesCreate are *not* blacklisted; we assume those are created during
      46             :  * CREATE TYPE, so they can't go away unless the enum type itself does.
      47             :  */
      48             : static HTAB *enum_blacklist = NULL;
      49             : 
      50             : static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems);
      51             : static int  sort_order_cmp(const void *p1, const void *p2);
      52             : 
      53             : 
      54             : /*
      55             :  * EnumValuesCreate
      56             :  *      Create an entry in pg_enum for each of the supplied enum values.
      57             :  *
      58             :  * vals is a list of Value strings.
      59             :  */
      60             : void
      61         348 : EnumValuesCreate(Oid enumTypeOid, List *vals)
      62             : {
      63             :     Relation    pg_enum;
      64             :     NameData    enumlabel;
      65             :     Oid        *oids;
      66             :     int         elemno,
      67             :                 num_elems;
      68             :     Datum       values[Natts_pg_enum];
      69             :     bool        nulls[Natts_pg_enum];
      70             :     ListCell   *lc;
      71             :     HeapTuple   tup;
      72             : 
      73         348 :     num_elems = list_length(vals);
      74             : 
      75             :     /*
      76             :      * We do not bother to check the list of values for duplicates --- if you
      77             :      * have any, you'll get a less-than-friendly unique-index violation. It is
      78             :      * probably not worth trying harder.
      79             :      */
      80             : 
      81         348 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
      82             : 
      83             :     /*
      84             :      * Allocate OIDs for the enum's members.
      85             :      *
      86             :      * While this method does not absolutely guarantee that we generate no
      87             :      * duplicate OIDs (since we haven't entered each oid into the table before
      88             :      * allocating the next), trouble could only occur if the OID counter wraps
      89             :      * all the way around before we finish. Which seems unlikely.
      90             :      */
      91         348 :     oids = (Oid *) palloc(num_elems * sizeof(Oid));
      92             : 
      93      250648 :     for (elemno = 0; elemno < num_elems; elemno++)
      94             :     {
      95             :         /*
      96             :          * We assign even-numbered OIDs to all the new enum labels.  This
      97             :          * tells the comparison functions the OIDs are in the correct sort
      98             :          * order and can be compared directly.
      99             :          */
     100             :         Oid         new_oid;
     101             : 
     102             :         do
     103             :         {
     104      500566 :             new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
     105             :                                          Anum_pg_enum_oid);
     106      500566 :         } while (new_oid & 1);
     107      250300 :         oids[elemno] = new_oid;
     108             :     }
     109             : 
     110             :     /* sort them, just in case OID counter wrapped from high to low */
     111         348 :     qsort(oids, num_elems, sizeof(Oid), oid_cmp);
     112             : 
     113             :     /* and make the entries */
     114         348 :     memset(nulls, false, sizeof(nulls));
     115             : 
     116         348 :     elemno = 0;
     117      250648 :     foreach(lc, vals)
     118             :     {
     119      250300 :         char       *lab = strVal(lfirst(lc));
     120             : 
     121             :         /*
     122             :          * labels are stored in a name field, for easier syscache lookup, so
     123             :          * check the length to make sure it's within range.
     124             :          */
     125      250300 :         if (strlen(lab) > (NAMEDATALEN - 1))
     126           0 :             ereport(ERROR,
     127             :                     (errcode(ERRCODE_INVALID_NAME),
     128             :                      errmsg("invalid enum label \"%s\"", lab),
     129             :                      errdetail("Labels must be %d characters or less.",
     130             :                                NAMEDATALEN - 1)));
     131             : 
     132      250300 :         values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
     133      250300 :         values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
     134      250300 :         values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
     135      250300 :         namestrcpy(&enumlabel, lab);
     136      250300 :         values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
     137             : 
     138      250300 :         tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
     139             : 
     140      250300 :         CatalogTupleInsert(pg_enum, tup);
     141      250300 :         heap_freetuple(tup);
     142             : 
     143      250300 :         elemno++;
     144             :     }
     145             : 
     146             :     /* clean up */
     147         348 :     pfree(oids);
     148         348 :     table_close(pg_enum, RowExclusiveLock);
     149         348 : }
     150             : 
     151             : 
     152             : /*
     153             :  * EnumValuesDelete
     154             :  *      Remove all the pg_enum entries for the specified enum type.
     155             :  */
     156             : void
     157         288 : EnumValuesDelete(Oid enumTypeOid)
     158             : {
     159             :     Relation    pg_enum;
     160             :     ScanKeyData key[1];
     161             :     SysScanDesc scan;
     162             :     HeapTuple   tup;
     163             : 
     164         288 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     165             : 
     166         288 :     ScanKeyInit(&key[0],
     167             :                 Anum_pg_enum_enumtypid,
     168             :                 BTEqualStrategyNumber, F_OIDEQ,
     169             :                 ObjectIdGetDatum(enumTypeOid));
     170             : 
     171         288 :     scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
     172             :                               NULL, 1, key);
     173             : 
     174      250702 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
     175             :     {
     176      250126 :         CatalogTupleDelete(pg_enum, &tup->t_self);
     177             :     }
     178             : 
     179         288 :     systable_endscan(scan);
     180             : 
     181         288 :     table_close(pg_enum, RowExclusiveLock);
     182         288 : }
     183             : 
     184             : /*
     185             :  * Initialize the enum blacklist for this transaction.
     186             :  */
     187             : static void
     188         166 : init_enum_blacklist(void)
     189             : {
     190             :     HASHCTL     hash_ctl;
     191             : 
     192         166 :     memset(&hash_ctl, 0, sizeof(hash_ctl));
     193         166 :     hash_ctl.keysize = sizeof(Oid);
     194         166 :     hash_ctl.entrysize = sizeof(Oid);
     195         166 :     hash_ctl.hcxt = TopTransactionContext;
     196         166 :     enum_blacklist = hash_create("Enum value blacklist",
     197             :                                  32,
     198             :                                  &hash_ctl,
     199             :                                  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     200         166 : }
     201             : 
     202             : /*
     203             :  * AddEnumLabel
     204             :  *      Add a new label to the enum set. By default it goes at
     205             :  *      the end, but the user can choose to place it before or
     206             :  *      after any existing set member.
     207             :  */
     208             : void
     209         264 : AddEnumLabel(Oid enumTypeOid,
     210             :              const char *newVal,
     211             :              const char *neighbor,
     212             :              bool newValIsAfter,
     213             :              bool skipIfExists)
     214             : {
     215             :     Relation    pg_enum;
     216             :     Oid         newOid;
     217             :     Datum       values[Natts_pg_enum];
     218             :     bool        nulls[Natts_pg_enum];
     219             :     NameData    enumlabel;
     220             :     HeapTuple   enum_tup;
     221             :     float4      newelemorder;
     222             :     HeapTuple  *existing;
     223             :     CatCList   *list;
     224             :     int         nelems;
     225             :     int         i;
     226             : 
     227             :     /* check length of new label is ok */
     228         264 :     if (strlen(newVal) > (NAMEDATALEN - 1))
     229           4 :         ereport(ERROR,
     230             :                 (errcode(ERRCODE_INVALID_NAME),
     231             :                  errmsg("invalid enum label \"%s\"", newVal),
     232             :                  errdetail("Labels must be %d characters or less.",
     233             :                            NAMEDATALEN - 1)));
     234             : 
     235             :     /*
     236             :      * Acquire a lock on the enum type, which we won't release until commit.
     237             :      * This ensures that two backends aren't concurrently modifying the same
     238             :      * enum type.  Without that, we couldn't be sure to get a consistent view
     239             :      * of the enum members via the syscache.  Note that this does not block
     240             :      * other backends from inspecting the type; see comments for
     241             :      * RenumberEnumType.
     242             :      */
     243         260 :     LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
     244             : 
     245             :     /*
     246             :      * Check if label is already in use.  The unique index on pg_enum would
     247             :      * catch this anyway, but we prefer a friendlier error message, and
     248             :      * besides we need a check to support IF NOT EXISTS.
     249             :      */
     250         260 :     enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
     251             :                                ObjectIdGetDatum(enumTypeOid),
     252             :                                CStringGetDatum(newVal));
     253         260 :     if (HeapTupleIsValid(enum_tup))
     254             :     {
     255           8 :         ReleaseSysCache(enum_tup);
     256           8 :         if (skipIfExists)
     257             :         {
     258           4 :             ereport(NOTICE,
     259             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     260             :                      errmsg("enum label \"%s\" already exists, skipping",
     261             :                             newVal)));
     262           4 :             return;
     263             :         }
     264             :         else
     265           4 :             ereport(ERROR,
     266             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     267             :                      errmsg("enum label \"%s\" already exists",
     268             :                             newVal)));
     269             :     }
     270             : 
     271         252 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     272             : 
     273             :     /* If we have to renumber the existing members, we restart from here */
     274             : restart:
     275             : 
     276             :     /* Get the list of existing members of the enum */
     277         256 :     list = SearchSysCacheList1(ENUMTYPOIDNAME,
     278             :                                ObjectIdGetDatum(enumTypeOid));
     279         256 :     nelems = list->n_members;
     280             : 
     281             :     /* Sort the existing members by enumsortorder */
     282         256 :     existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple));
     283        3608 :     for (i = 0; i < nelems; i++)
     284        3352 :         existing[i] = &(list->members[i]->tuple);
     285             : 
     286         256 :     qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
     287             : 
     288         256 :     if (neighbor == NULL)
     289             :     {
     290             :         /*
     291             :          * Put the new label at the end of the list. No change to existing
     292             :          * tuples is required.
     293             :          */
     294         106 :         if (nelems > 0)
     295             :         {
     296         102 :             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
     297             : 
     298         102 :             newelemorder = en->enumsortorder + 1;
     299             :         }
     300             :         else
     301           4 :             newelemorder = 1;
     302             :     }
     303             :     else
     304             :     {
     305             :         /* BEFORE or AFTER was specified */
     306             :         int         nbr_index;
     307             :         int         other_nbr_index;
     308             :         Form_pg_enum nbr_en;
     309             :         Form_pg_enum other_nbr_en;
     310             : 
     311             :         /* Locate the neighbor element */
     312        2194 :         for (nbr_index = 0; nbr_index < nelems; nbr_index++)
     313             :         {
     314        2190 :             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
     315             : 
     316        2190 :             if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
     317         146 :                 break;
     318             :         }
     319         150 :         if (nbr_index >= nelems)
     320           4 :             ereport(ERROR,
     321             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     322             :                      errmsg("\"%s\" is not an existing enum label",
     323             :                             neighbor)));
     324         146 :         nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
     325             : 
     326             :         /*
     327             :          * Attempt to assign an appropriate enumsortorder value: one less than
     328             :          * the smallest member, one more than the largest member, or halfway
     329             :          * between two existing members.
     330             :          *
     331             :          * In the "halfway" case, because of the finite precision of float4,
     332             :          * we might compute a value that's actually equal to one or the other
     333             :          * of its neighbors.  In that case we renumber the existing members
     334             :          * and try again.
     335             :          */
     336         146 :         if (newValIsAfter)
     337          12 :             other_nbr_index = nbr_index + 1;
     338             :         else
     339         134 :             other_nbr_index = nbr_index - 1;
     340             : 
     341         146 :         if (other_nbr_index < 0)
     342           6 :             newelemorder = nbr_en->enumsortorder - 1;
     343         140 :         else if (other_nbr_index >= nelems)
     344           6 :             newelemorder = nbr_en->enumsortorder + 1;
     345             :         else
     346             :         {
     347             :             /*
     348             :              * The midpoint value computed here has to be rounded to float4
     349             :              * precision, else our equality comparisons against the adjacent
     350             :              * values are meaningless.  The most portable way of forcing that
     351             :              * to happen with non-C-standard-compliant compilers is to store
     352             :              * it into a volatile variable.
     353             :              */
     354             :             volatile float4 midpoint;
     355             : 
     356         134 :             other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
     357         402 :             midpoint = (nbr_en->enumsortorder +
     358         268 :                         other_nbr_en->enumsortorder) / 2;
     359             : 
     360         264 :             if (midpoint == nbr_en->enumsortorder ||
     361         130 :                 midpoint == other_nbr_en->enumsortorder)
     362             :             {
     363           4 :                 RenumberEnumType(pg_enum, existing, nelems);
     364             :                 /* Clean up and start over */
     365           4 :                 pfree(existing);
     366           4 :                 ReleaseCatCacheList(list);
     367           4 :                 goto restart;
     368             :             }
     369             : 
     370         130 :             newelemorder = midpoint;
     371             :         }
     372             :     }
     373             : 
     374             :     /* Get a new OID for the new label */
     375         248 :     if (IsBinaryUpgrade)
     376             :     {
     377          82 :         if (!OidIsValid(binary_upgrade_next_pg_enum_oid))
     378           0 :             ereport(ERROR,
     379             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     380             :                      errmsg("pg_enum OID value not set when in binary upgrade mode")));
     381             : 
     382             :         /*
     383             :          * Use binary-upgrade override for pg_enum.oid, if supplied. During
     384             :          * binary upgrade, all pg_enum.oid's are set this way so they are
     385             :          * guaranteed to be consistent.
     386             :          */
     387          82 :         if (neighbor != NULL)
     388           0 :             ereport(ERROR,
     389             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     390             :                      errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade")));
     391             : 
     392          82 :         newOid = binary_upgrade_next_pg_enum_oid;
     393          82 :         binary_upgrade_next_pg_enum_oid = InvalidOid;
     394             :     }
     395             :     else
     396             :     {
     397             :         /*
     398             :          * Normal case: we need to allocate a new Oid for the value.
     399             :          *
     400             :          * We want to give the new element an even-numbered Oid if it's safe,
     401             :          * which is to say it compares correctly to all pre-existing even
     402             :          * numbered Oids in the enum.  Otherwise, we must give it an odd Oid.
     403             :          */
     404             :         for (;;)
     405         142 :         {
     406             :             bool        sorts_ok;
     407             : 
     408             :             /* Get a new OID (different from all existing pg_enum tuples) */
     409         308 :             newOid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
     410             :                                         Anum_pg_enum_oid);
     411             : 
     412             :             /*
     413             :              * Detect whether it sorts correctly relative to existing
     414             :              * even-numbered labels of the enum.  We can ignore existing
     415             :              * labels with odd Oids, since a comparison involving one of those
     416             :              * will not take the fast path anyway.
     417             :              */
     418         308 :             sorts_ok = true;
     419        4154 :             for (i = 0; i < nelems; i++)
     420             :             {
     421        4102 :                 HeapTuple   exists_tup = existing[i];
     422        4102 :                 Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
     423        4102 :                 Oid         exists_oid = exists_en->oid;
     424             : 
     425        4102 :                 if (exists_oid & 1)
     426        3440 :                     continue;   /* ignore odd Oids */
     427             : 
     428         662 :                 if (exists_en->enumsortorder < newelemorder)
     429             :                 {
     430             :                     /* should sort before */
     431         406 :                     if (exists_oid >= newOid)
     432             :                     {
     433           0 :                         sorts_ok = false;
     434           0 :                         break;
     435             :                     }
     436             :                 }
     437             :                 else
     438             :                 {
     439             :                     /* should sort after */
     440         256 :                     if (exists_oid <= newOid)
     441             :                     {
     442         256 :                         sorts_ok = false;
     443         256 :                         break;
     444             :                     }
     445             :                 }
     446             :             }
     447             : 
     448         308 :             if (sorts_ok)
     449             :             {
     450             :                 /* If it's even and sorts OK, we're done. */
     451          52 :                 if ((newOid & 1) == 0)
     452          30 :                     break;
     453             : 
     454             :                 /*
     455             :                  * If it's odd, and sorts OK, loop back to get another OID and
     456             :                  * try again.  Probably, the next available even OID will sort
     457             :                  * correctly too, so it's worth trying.
     458             :                  */
     459             :             }
     460             :             else
     461             :             {
     462             :                 /*
     463             :                  * If it's odd, and does not sort correctly, we're done.
     464             :                  * (Probably, the next available even OID would sort
     465             :                  * incorrectly too, so no point in trying again.)
     466             :                  */
     467         256 :                 if (newOid & 1)
     468         136 :                     break;
     469             : 
     470             :                 /*
     471             :                  * If it's even, and does not sort correctly, loop back to get
     472             :                  * another OID and try again.  (We *must* reject this case.)
     473             :                  */
     474             :             }
     475             :         }
     476             :     }
     477             : 
     478             :     /* Done with info about existing members */
     479         248 :     pfree(existing);
     480         248 :     ReleaseCatCacheList(list);
     481             : 
     482             :     /* Create the new pg_enum entry */
     483         248 :     memset(nulls, false, sizeof(nulls));
     484         248 :     values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
     485         248 :     values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
     486         248 :     values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
     487         248 :     namestrcpy(&enumlabel, newVal);
     488         248 :     values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
     489         248 :     enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
     490         248 :     CatalogTupleInsert(pg_enum, enum_tup);
     491         248 :     heap_freetuple(enum_tup);
     492             : 
     493         248 :     table_close(pg_enum, RowExclusiveLock);
     494             : 
     495             :     /* Set up the blacklist hash if not already done in this transaction */
     496         248 :     if (enum_blacklist == NULL)
     497         166 :         init_enum_blacklist();
     498             : 
     499             :     /* Add the new value to the blacklist */
     500         248 :     (void) hash_search(enum_blacklist, &newOid, HASH_ENTER, NULL);
     501             : }
     502             : 
     503             : 
     504             : /*
     505             :  * RenameEnumLabel
     506             :  *      Rename a label in an enum set.
     507             :  */
     508             : void
     509          16 : RenameEnumLabel(Oid enumTypeOid,
     510             :                 const char *oldVal,
     511             :                 const char *newVal)
     512             : {
     513             :     Relation    pg_enum;
     514             :     HeapTuple   enum_tup;
     515             :     Form_pg_enum en;
     516             :     CatCList   *list;
     517             :     int         nelems;
     518             :     HeapTuple   old_tup;
     519             :     bool        found_new;
     520             :     int         i;
     521             : 
     522             :     /* check length of new label is ok */
     523          16 :     if (strlen(newVal) > (NAMEDATALEN - 1))
     524           0 :         ereport(ERROR,
     525             :                 (errcode(ERRCODE_INVALID_NAME),
     526             :                  errmsg("invalid enum label \"%s\"", newVal),
     527             :                  errdetail("Labels must be %d characters or less.",
     528             :                            NAMEDATALEN - 1)));
     529             : 
     530             :     /*
     531             :      * Acquire a lock on the enum type, which we won't release until commit.
     532             :      * This ensures that two backends aren't concurrently modifying the same
     533             :      * enum type.  Since we are not changing the type's sort order, this is
     534             :      * probably not really necessary, but there seems no reason not to take
     535             :      * the lock to be sure.
     536             :      */
     537          16 :     LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
     538             : 
     539          16 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     540             : 
     541             :     /* Get the list of existing members of the enum */
     542          16 :     list = SearchSysCacheList1(ENUMTYPOIDNAME,
     543             :                                ObjectIdGetDatum(enumTypeOid));
     544          16 :     nelems = list->n_members;
     545             : 
     546             :     /*
     547             :      * Locate the element to rename and check if the new label is already in
     548             :      * use.  (The unique index on pg_enum would catch that anyway, but we
     549             :      * prefer a friendlier error message.)
     550             :      */
     551          16 :     old_tup = NULL;
     552          16 :     found_new = false;
     553          96 :     for (i = 0; i < nelems; i++)
     554             :     {
     555          80 :         enum_tup = &(list->members[i]->tuple);
     556          80 :         en = (Form_pg_enum) GETSTRUCT(enum_tup);
     557          80 :         if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
     558          12 :             old_tup = enum_tup;
     559          80 :         if (strcmp(NameStr(en->enumlabel), newVal) == 0)
     560           8 :             found_new = true;
     561             :     }
     562          16 :     if (!old_tup)
     563           4 :         ereport(ERROR,
     564             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     565             :                  errmsg("\"%s\" is not an existing enum label",
     566             :                         oldVal)));
     567          12 :     if (found_new)
     568           4 :         ereport(ERROR,
     569             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     570             :                  errmsg("enum label \"%s\" already exists",
     571             :                         newVal)));
     572             : 
     573             :     /* OK, make a writable copy of old tuple */
     574           8 :     enum_tup = heap_copytuple(old_tup);
     575           8 :     en = (Form_pg_enum) GETSTRUCT(enum_tup);
     576             : 
     577           8 :     ReleaseCatCacheList(list);
     578             : 
     579             :     /* Update the pg_enum entry */
     580           8 :     namestrcpy(&en->enumlabel, newVal);
     581           8 :     CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
     582           8 :     heap_freetuple(enum_tup);
     583             : 
     584           8 :     table_close(pg_enum, RowExclusiveLock);
     585           8 : }
     586             : 
     587             : 
     588             : /*
     589             :  * Test if the given enum value is on the blacklist
     590             :  */
     591             : bool
     592          52 : EnumBlacklisted(Oid enum_id)
     593             : {
     594             :     bool        found;
     595             : 
     596             :     /* If we've made no blacklist table, all values are safe */
     597          52 :     if (enum_blacklist == NULL)
     598          28 :         return false;
     599             : 
     600             :     /* Else, is it in the table? */
     601          24 :     (void) hash_search(enum_blacklist, &enum_id, HASH_FIND, &found);
     602          24 :     return found;
     603             : }
     604             : 
     605             : 
     606             : /*
     607             :  * Clean up enum stuff after end of top-level transaction.
     608             :  */
     609             : void
     610      459926 : AtEOXact_Enum(void)
     611             : {
     612             :     /*
     613             :      * Reset the blacklist table, as all our enum values are now committed.
     614             :      * The memory will go away automatically when TopTransactionContext is
     615             :      * freed; it's sufficient to clear our pointer.
     616             :      */
     617      459926 :     enum_blacklist = NULL;
     618      459926 : }
     619             : 
     620             : 
     621             : /*
     622             :  * RenumberEnumType
     623             :  *      Renumber existing enum elements to have sort positions 1..n.
     624             :  *
     625             :  * We avoid doing this unless absolutely necessary; in most installations
     626             :  * it will never happen.  The reason is that updating existing pg_enum
     627             :  * entries creates hazards for other backends that are concurrently reading
     628             :  * pg_enum.  Although system catalog scans now use MVCC semantics, the
     629             :  * syscache machinery might read different pg_enum entries under different
     630             :  * snapshots, so some other backend might get confused about the proper
     631             :  * ordering if a concurrent renumbering occurs.
     632             :  *
     633             :  * We therefore make the following choices:
     634             :  *
     635             :  * 1. Any code that is interested in the enumsortorder values MUST read
     636             :  * all the relevant pg_enum entries with a single MVCC snapshot, or else
     637             :  * acquire lock on the enum type to prevent concurrent execution of
     638             :  * AddEnumLabel().
     639             :  *
     640             :  * 2. Code that is not examining enumsortorder can use a syscache
     641             :  * (for example, enum_in and enum_out do so).
     642             :  */
     643             : static void
     644           4 : RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems)
     645             : {
     646             :     int         i;
     647             : 
     648             :     /*
     649             :      * We should only need to increase existing elements' enumsortorders,
     650             :      * never decrease them.  Therefore, work from the end backwards, to avoid
     651             :      * unwanted uniqueness violations.
     652             :      */
     653         104 :     for (i = nelems - 1; i >= 0; i--)
     654             :     {
     655             :         HeapTuple   newtup;
     656             :         Form_pg_enum en;
     657             :         float4      newsortorder;
     658             : 
     659         100 :         newtup = heap_copytuple(existing[i]);
     660         100 :         en = (Form_pg_enum) GETSTRUCT(newtup);
     661             : 
     662         100 :         newsortorder = i + 1;
     663         100 :         if (en->enumsortorder != newsortorder)
     664             :         {
     665          96 :             en->enumsortorder = newsortorder;
     666             : 
     667          96 :             CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
     668             :         }
     669             : 
     670         100 :         heap_freetuple(newtup);
     671             :     }
     672             : 
     673             :     /* Make the updates visible */
     674           4 :     CommandCounterIncrement();
     675           4 : }
     676             : 
     677             : 
     678             : /* qsort comparison function for tuples by sort order */
     679             : static int
     680       13126 : sort_order_cmp(const void *p1, const void *p2)
     681             : {
     682       13126 :     HeapTuple   v1 = *((const HeapTuple *) p1);
     683       13126 :     HeapTuple   v2 = *((const HeapTuple *) p2);
     684       13126 :     Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
     685       13126 :     Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
     686             : 
     687       13126 :     if (en1->enumsortorder < en2->enumsortorder)
     688        5588 :         return -1;
     689        7538 :     else if (en1->enumsortorder > en2->enumsortorder)
     690        7538 :         return 1;
     691             :     else
     692           0 :         return 0;
     693             : }
     694             : 
     695             : Size
     696         490 : EstimateEnumBlacklistSpace(void)
     697             : {
     698             :     size_t      entries;
     699             : 
     700         490 :     if (enum_blacklist)
     701           0 :         entries = hash_get_num_entries(enum_blacklist);
     702             :     else
     703         490 :         entries = 0;
     704             : 
     705             :     /* Add one for the terminator. */
     706         490 :     return sizeof(Oid) * (entries + 1);
     707             : }
     708             : 
     709             : void
     710         490 : SerializeEnumBlacklist(void *space, Size size)
     711             : {
     712         490 :     Oid        *serialized = (Oid *) space;
     713             : 
     714             :     /*
     715             :      * Make sure the hash table hasn't changed in size since the caller
     716             :      * reserved the space.
     717             :      */
     718             :     Assert(size == EstimateEnumBlacklistSpace());
     719             : 
     720             :     /* Write out all the values from the hash table, if there is one. */
     721         490 :     if (enum_blacklist)
     722             :     {
     723             :         HASH_SEQ_STATUS status;
     724             :         Oid        *value;
     725             : 
     726           0 :         hash_seq_init(&status, enum_blacklist);
     727           0 :         while ((value = (Oid *) hash_seq_search(&status)))
     728           0 :             *serialized++ = *value;
     729             :     }
     730             : 
     731             :     /* Write out the terminator. */
     732         490 :     *serialized = InvalidOid;
     733             : 
     734             :     /*
     735             :      * Make sure the amount of space we actually used matches what was
     736             :      * estimated.
     737             :      */
     738             :     Assert((char *) (serialized + 1) == ((char *) space) + size);
     739         490 : }
     740             : 
     741             : void
     742        1616 : RestoreEnumBlacklist(void *space)
     743             : {
     744        1616 :     Oid        *serialized = (Oid *) space;
     745             : 
     746             :     Assert(!enum_blacklist);
     747             : 
     748             :     /*
     749             :      * As a special case, if the list is empty then don't even bother to
     750             :      * create the hash table.  This is the usual case, since enum alteration
     751             :      * is expected to be rare.
     752             :      */
     753        1616 :     if (!OidIsValid(*serialized))
     754        1616 :         return;
     755             : 
     756             :     /* Read all the values into a new hash table. */
     757           0 :     init_enum_blacklist();
     758             :     do
     759             :     {
     760           0 :         hash_search(enum_blacklist, serialized++, HASH_ENTER, NULL);
     761           0 :     } while (OidIsValid(*serialized));
     762             : }

Generated by: LCOV version 1.13