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

Generated by: LCOV version 1.13