LCOV - code coverage report
Current view: top level - src/backend/catalog - pg_enum.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 92.0 % 262 241
Test Date: 2026-03-01 18:15:11 Functions: 100.0 % 14 14
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-2026, 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          221 : EnumValuesCreate(Oid enumTypeOid, List *vals)
      85              : {
      86              :     Relation    pg_enum;
      87              :     Oid        *oids;
      88              :     int         elemno,
      89              :                 num_elems;
      90              :     ListCell   *lc;
      91          221 :     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          221 :     if (GetCurrentTransactionNestLevel() == 1)
     104              :     {
     105          221 :         if (uncommitted_enum_types == NULL)
     106          218 :             init_uncommitted_enum_types();
     107          221 :         (void) hash_search(uncommitted_enum_types, &enumTypeOid,
     108              :                            HASH_ENTER, NULL);
     109              :     }
     110              : 
     111          221 :     num_elems = list_length(vals);
     112              : 
     113          221 :     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          221 :     oids = palloc_array(Oid, num_elems);
     124              : 
     125       125525 :     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       250569 :             new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId,
     137              :                                          Anum_pg_enum_oid);
     138       250569 :         } while (new_oid & 1);
     139       125304 :         oids[elemno] = new_oid;
     140              :     }
     141              : 
     142              :     /* sort them, just in case OID counter wrapped from high to low */
     143          221 :     qsort(oids, num_elems, sizeof(Oid), oid_cmp);
     144              : 
     145              :     /* and make the entries */
     146          221 :     indstate = CatalogOpenIndexes(pg_enum);
     147              : 
     148              :     /* allocate the slots to use and initialize them */
     149          221 :     nslots = Min(num_elems,
     150              :                  MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_enum));
     151          221 :     slot = palloc_array(TupleTableSlot *, nslots);
     152       108275 :     for (int i = 0; i < nslots; i++)
     153       108054 :         slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(pg_enum),
     154              :                                            &TTSOpsHeapTuple);
     155              : 
     156          221 :     elemno = 0;
     157       125522 :     foreach(lc, vals)
     158              :     {
     159       125304 :         char       *lab = strVal(lfirst(lc));
     160       125304 :         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       125304 :         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     62563351 :         foreach(lc2, vals)
     179              :         {
     180              :             /* Only need to compare lc to earlier entries */
     181     62563351 :             if (lc2 == lc)
     182       125301 :                 break;
     183              : 
     184     62438050 :             if (strcmp(lab, strVal(lfirst(lc2))) == 0)
     185            3 :                 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       125301 :         ExecClearTuple(slot[slotCount]);
     193              : 
     194       125301 :         memset(slot[slotCount]->tts_isnull, false,
     195       125301 :                slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
     196              : 
     197       125301 :         slot[slotCount]->tts_values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]);
     198       125301 :         slot[slotCount]->tts_values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
     199       125301 :         slot[slotCount]->tts_values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1);
     200              : 
     201       125301 :         namestrcpy(enumlabel, lab);
     202       125301 :         slot[slotCount]->tts_values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(enumlabel);
     203              : 
     204       125301 :         ExecStoreVirtualTuple(slot[slotCount]);
     205       125301 :         slotCount++;
     206              : 
     207              :         /* if slots are full, insert a batch of tuples */
     208       125301 :         if (slotCount == nslots)
     209              :         {
     210          214 :             CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
     211              :                                              indstate);
     212          214 :             slotCount = 0;
     213              :         }
     214              : 
     215       125301 :         elemno++;
     216              :     }
     217              : 
     218              :     /* Insert any tuples left in the buffer */
     219          218 :     if (slotCount > 0)
     220          125 :         CatalogTuplesMultiInsertWithInfo(pg_enum, slot, slotCount,
     221              :                                          indstate);
     222              : 
     223              :     /* clean up */
     224          218 :     pfree(oids);
     225       108263 :     for (int i = 0; i < nslots; i++)
     226       108045 :         ExecDropSingleTupleTableSlot(slot[i]);
     227          218 :     CatalogCloseIndexes(indstate);
     228          218 :     table_close(pg_enum, RowExclusiveLock);
     229          218 : }
     230              : 
     231              : 
     232              : /*
     233              :  * EnumValuesDelete
     234              :  *      Remove all the pg_enum entries for the specified enum type.
     235              :  */
     236              : void
     237          162 : EnumValuesDelete(Oid enumTypeOid)
     238              : {
     239              :     Relation    pg_enum;
     240              :     ScanKeyData key[1];
     241              :     SysScanDesc scan;
     242              :     HeapTuple   tup;
     243              : 
     244          162 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     245              : 
     246          162 :     ScanKeyInit(&key[0],
     247              :                 Anum_pg_enum_enumtypid,
     248              :                 BTEqualStrategyNumber, F_OIDEQ,
     249              :                 ObjectIdGetDatum(enumTypeOid));
     250              : 
     251          162 :     scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true,
     252              :                               NULL, 1, key);
     253              : 
     254       125276 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
     255              :     {
     256       125114 :         CatalogTupleDelete(pg_enum, &tup->t_self);
     257              :     }
     258              : 
     259          162 :     systable_endscan(scan);
     260              : 
     261          162 :     table_close(pg_enum, RowExclusiveLock);
     262          162 : }
     263              : 
     264              : /*
     265              :  * Initialize the uncommitted enum types table for this transaction.
     266              :  */
     267              : static void
     268          218 : init_uncommitted_enum_types(void)
     269              : {
     270              :     HASHCTL     hash_ctl;
     271              : 
     272          218 :     hash_ctl.keysize = sizeof(Oid);
     273          218 :     hash_ctl.entrysize = sizeof(Oid);
     274          218 :     hash_ctl.hcxt = TopTransactionContext;
     275          218 :     uncommitted_enum_types = hash_create("Uncommitted enum types",
     276              :                                          32,
     277              :                                          &hash_ctl,
     278              :                                          HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     279          218 : }
     280              : 
     281              : /*
     282              :  * Initialize the uncommitted enum values table for this transaction.
     283              :  */
     284              : static void
     285          118 : init_uncommitted_enum_values(void)
     286              : {
     287              :     HASHCTL     hash_ctl;
     288              : 
     289          118 :     hash_ctl.keysize = sizeof(Oid);
     290          118 :     hash_ctl.entrysize = sizeof(Oid);
     291          118 :     hash_ctl.hcxt = TopTransactionContext;
     292          118 :     uncommitted_enum_values = hash_create("Uncommitted enum values",
     293              :                                           32,
     294              :                                           &hash_ctl,
     295              :                                           HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     296          118 : }
     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          186 : 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          186 :     if (strlen(newVal) > (NAMEDATALEN - 1))
     325            3 :         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          183 :     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          183 :     enum_tup = SearchSysCache2(ENUMTYPOIDNAME,
     347              :                                ObjectIdGetDatum(enumTypeOid),
     348              :                                CStringGetDatum(newVal));
     349          183 :     if (HeapTupleIsValid(enum_tup))
     350              :     {
     351            6 :         ReleaseSysCache(enum_tup);
     352            6 :         if (skipIfExists)
     353              :         {
     354            3 :             ereport(NOTICE,
     355              :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     356              :                      errmsg("enum label \"%s\" already exists, skipping",
     357              :                             newVal)));
     358           59 :             return;
     359              :         }
     360              :         else
     361            3 :             ereport(ERROR,
     362              :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     363              :                      errmsg("enum label \"%s\" already exists",
     364              :                             newVal)));
     365              :     }
     366              : 
     367          177 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     368              : 
     369              :     /* If we have to renumber the existing members, we restart from here */
     370          180 : restart:
     371              : 
     372              :     /* Get the list of existing members of the enum */
     373          180 :     list = SearchSysCacheList1(ENUMTYPOIDNAME,
     374              :                                ObjectIdGetDatum(enumTypeOid));
     375          180 :     nelems = list->n_members;
     376              : 
     377              :     /* Sort the existing members by enumsortorder */
     378          180 :     existing = palloc_array(HeapTuple, nelems);
     379         2447 :     for (i = 0; i < nelems; i++)
     380         2267 :         existing[i] = &(list->members[i]->tuple);
     381              : 
     382          180 :     qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp);
     383              : 
     384          180 :     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           68 :         if (nelems > 0)
     391              :         {
     392           64 :             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]);
     393              : 
     394           64 :             newelemorder = en->enumsortorder + 1;
     395              :         }
     396              :         else
     397            4 :             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         1646 :         for (nbr_index = 0; nbr_index < nelems; nbr_index++)
     409              :         {
     410         1643 :             Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]);
     411              : 
     412         1643 :             if (strcmp(NameStr(en->enumlabel), neighbor) == 0)
     413          109 :                 break;
     414              :         }
     415          112 :         if (nbr_index >= nelems)
     416            3 :             ereport(ERROR,
     417              :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     418              :                      errmsg("\"%s\" is not an existing enum label",
     419              :                             neighbor)));
     420          109 :         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          109 :         if (newValIsAfter)
     433            8 :             other_nbr_index = nbr_index + 1;
     434              :         else
     435          101 :             other_nbr_index = nbr_index - 1;
     436              : 
     437          109 :         if (other_nbr_index < 0)
     438            4 :             newelemorder = nbr_en->enumsortorder - 1;
     439          105 :         else if (other_nbr_index >= nelems)
     440            4 :             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          101 :             other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]);
     453          101 :             midpoint = (nbr_en->enumsortorder +
     454          101 :                         other_nbr_en->enumsortorder) / 2;
     455              : 
     456          101 :             if (midpoint == nbr_en->enumsortorder ||
     457           98 :                 midpoint == other_nbr_en->enumsortorder)
     458              :             {
     459            3 :                 RenumberEnumType(pg_enum, existing, nelems);
     460              :                 /* Clean up and start over */
     461            3 :                 pfree(existing);
     462            3 :                 ReleaseCatCacheList(list);
     463            3 :                 goto restart;
     464              :             }
     465              : 
     466           98 :             newelemorder = midpoint;
     467              :         }
     468              :     }
     469              : 
     470              :     /* Get a new OID for the new label */
     471          174 :     if (IsBinaryUpgrade)
     472              :     {
     473           50 :         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           50 :         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           50 :         newOid = binary_upgrade_next_pg_enum_oid;
     489           50 :         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           97 :         {
     502              :             bool        sorts_ok;
     503              : 
     504              :             /* Get a new OID (different from all existing pg_enum tuples) */
     505          221 :             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          221 :             sorts_ok = true;
     515         2880 :             for (i = 0; i < nelems; i++)
     516              :             {
     517         2840 :                 HeapTuple   exists_tup = existing[i];
     518         2840 :                 Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup);
     519         2840 :                 Oid         exists_oid = exists_en->oid;
     520              : 
     521         2840 :                 if (exists_oid & 1)
     522         2362 :                     continue;   /* ignore odd Oids */
     523              : 
     524          478 :                 if (exists_en->enumsortorder < newelemorder)
     525              :                 {
     526              :                     /* should sort before */
     527          297 :                     if (exists_oid >= newOid)
     528              :                     {
     529            0 :                         sorts_ok = false;
     530            0 :                         break;
     531              :                     }
     532              :                 }
     533              :                 else
     534              :                 {
     535              :                     /* should sort after */
     536          181 :                     if (exists_oid <= newOid)
     537              :                     {
     538          181 :                         sorts_ok = false;
     539          181 :                         break;
     540              :                     }
     541              :                 }
     542              :             }
     543              : 
     544          221 :             if (sorts_ok)
     545              :             {
     546              :                 /* If it's even and sorts OK, we're done. */
     547           40 :                 if ((newOid & 1) == 0)
     548           22 :                     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          181 :                 if (newOid & 1)
     564          102 :                     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          174 :     pfree(existing);
     576          174 :     ReleaseCatCacheList(list);
     577              : 
     578              :     /* Create the new pg_enum entry */
     579          174 :     memset(nulls, false, sizeof(nulls));
     580          174 :     values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid);
     581          174 :     values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid);
     582          174 :     values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder);
     583          174 :     namestrcpy(&enumlabel, newVal);
     584          174 :     values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel);
     585          174 :     enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls);
     586          174 :     CatalogTupleInsert(pg_enum, enum_tup);
     587          174 :     heap_freetuple(enum_tup);
     588              : 
     589          174 :     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          348 :     if (GetCurrentTransactionNestLevel() == 1 &&
     603          174 :         EnumTypeUncommitted(enumTypeOid))
     604           56 :         return;
     605              : 
     606              :     /* Set up the uncommitted values table if not already done in this tx */
     607          118 :     if (uncommitted_enum_values == NULL)
     608          118 :         init_uncommitted_enum_values();
     609              : 
     610              :     /* Add the new value to the table */
     611          118 :     (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           12 : 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           12 :     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           12 :     LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock);
     649              : 
     650           12 :     pg_enum = table_open(EnumRelationId, RowExclusiveLock);
     651              : 
     652              :     /* Get the list of existing members of the enum */
     653           12 :     list = SearchSysCacheList1(ENUMTYPOIDNAME,
     654              :                                ObjectIdGetDatum(enumTypeOid));
     655           12 :     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           12 :     old_tup = NULL;
     663           12 :     found_new = false;
     664           72 :     for (i = 0; i < nelems; i++)
     665              :     {
     666           60 :         enum_tup = &(list->members[i]->tuple);
     667           60 :         en = (Form_pg_enum) GETSTRUCT(enum_tup);
     668           60 :         if (strcmp(NameStr(en->enumlabel), oldVal) == 0)
     669            9 :             old_tup = enum_tup;
     670           60 :         if (strcmp(NameStr(en->enumlabel), newVal) == 0)
     671            6 :             found_new = true;
     672              :     }
     673           12 :     if (!old_tup)
     674            3 :         ereport(ERROR,
     675              :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     676              :                  errmsg("\"%s\" is not an existing enum label",
     677              :                         oldVal)));
     678            9 :     if (found_new)
     679            3 :         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            6 :     enum_tup = heap_copytuple(old_tup);
     686            6 :     en = (Form_pg_enum) GETSTRUCT(enum_tup);
     687              : 
     688            6 :     ReleaseCatCacheList(list);
     689              : 
     690              :     /* Update the pg_enum entry */
     691            6 :     namestrcpy(&en->enumlabel, newVal);
     692            6 :     CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup);
     693            6 :     heap_freetuple(enum_tup);
     694              : 
     695            6 :     table_close(pg_enum, RowExclusiveLock);
     696            6 : }
     697              : 
     698              : 
     699              : /*
     700              :  * Test if the given type OID is in the table of uncommitted enum types.
     701              :  */
     702              : static bool
     703          174 : 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          174 :     if (uncommitted_enum_types == NULL)
     709          118 :         return false;
     710              : 
     711              :     /* Else, is it in the table? */
     712           56 :     (void) hash_search(uncommitted_enum_types, &typ_id, HASH_FIND, &found);
     713           56 :     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           45 : 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           45 :     if (uncommitted_enum_values == NULL)
     727           33 :         return false;
     728              : 
     729              :     /* Else, is it in the table? */
     730           12 :     (void) hash_search(uncommitted_enum_values, &enum_id, HASH_FIND, &found);
     731           12 :     return found;
     732              : }
     733              : 
     734              : 
     735              : /*
     736              :  * Clean up enum stuff after end of top-level transaction.
     737              :  */
     738              : void
     739       577677 : 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       577677 :     uncommitted_enum_types = NULL;
     747       577677 :     uncommitted_enum_values = NULL;
     748       577677 : }
     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            3 : 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           78 :     for (i = nelems - 1; i >= 0; i--)
     784              :     {
     785              :         HeapTuple   newtup;
     786              :         Form_pg_enum en;
     787              :         float4      newsortorder;
     788              : 
     789           75 :         newtup = heap_copytuple(existing[i]);
     790           75 :         en = (Form_pg_enum) GETSTRUCT(newtup);
     791              : 
     792           75 :         newsortorder = i + 1;
     793           75 :         if (en->enumsortorder != newsortorder)
     794              :         {
     795           72 :             en->enumsortorder = newsortorder;
     796              : 
     797           72 :             CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup);
     798              :         }
     799              : 
     800           75 :         heap_freetuple(newtup);
     801              :     }
     802              : 
     803              :     /* Make the updates visible */
     804            3 :     CommandCounterIncrement();
     805            3 : }
     806              : 
     807              : 
     808              : /* qsort comparison function for tuples by sort order */
     809              : static int
     810         8831 : sort_order_cmp(const void *p1, const void *p2)
     811              : {
     812         8831 :     HeapTuple   v1 = *((const HeapTuple *) p1);
     813         8831 :     HeapTuple   v2 = *((const HeapTuple *) p2);
     814         8831 :     Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1);
     815         8831 :     Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2);
     816              : 
     817         8831 :     if (en1->enumsortorder < en2->enumsortorder)
     818         3737 :         return -1;
     819         5094 :     else if (en1->enumsortorder > en2->enumsortorder)
     820         5094 :         return 1;
     821              :     else
     822            0 :         return 0;
     823              : }
     824              : 
     825              : Size
     826          498 : EstimateUncommittedEnumsSpace(void)
     827              : {
     828          498 :     size_t      entries = 0;
     829              : 
     830          498 :     if (uncommitted_enum_types)
     831            0 :         entries += hash_get_num_entries(uncommitted_enum_types);
     832          498 :     if (uncommitted_enum_values)
     833            0 :         entries += hash_get_num_entries(uncommitted_enum_values);
     834              : 
     835              :     /* Add two for the terminators. */
     836          498 :     return sizeof(Oid) * (entries + 2);
     837              : }
     838              : 
     839              : void
     840          498 : SerializeUncommittedEnums(void *space, Size size)
     841              : {
     842          498 :     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          498 :     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          498 :     *serialized++ = InvalidOid;
     863              : 
     864              :     /* Write out all the OIDs from the values hash table, if there is one. */
     865          498 :     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          498 :     *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          498 : }
     884              : 
     885              : void
     886         1493 : RestoreUncommittedEnums(void *space)
     887              : {
     888         1493 :     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         1493 :     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         1493 :     serialized++;
     909         1493 :     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         1493 : }
        

Generated by: LCOV version 2.0-1