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

Generated by: LCOV version 1.14