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

Generated by: LCOV version 1.16