LCOV - differential code coverage report
Current view: top level - src/backend/catalog - pg_enum.c (source / functions) Coverage Total Hit LBC UIC UBC GBC GIC GNC CBC EUB ECB DUB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 93.9 % 229 215 3 10 1 2 123 25 65 10 143 1 4
Current Date: 2023-04-08 15:15:32 Functions: 100.0 % 12 12 11 1 11
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a