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

Generated by: LCOV version 1.14