LCOV - code coverage report
Current view: top level - src/backend/utils/adt - array_expanded.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.9 % 147 141
Test Date: 2026-03-01 15:14:58 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * array_expanded.c
       4              :  *    Basic functions for manipulating expanded arrays.
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/utils/adt/array_expanded.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : #include "postgres.h"
      16              : 
      17              : #include "access/tupmacs.h"
      18              : #include "utils/array.h"
      19              : #include "utils/lsyscache.h"
      20              : #include "utils/memutils.h"
      21              : 
      22              : 
      23              : /* "Methods" required for an expanded object */
      24              : static Size EA_get_flat_size(ExpandedObjectHeader *eohptr);
      25              : static void EA_flatten_into(ExpandedObjectHeader *eohptr,
      26              :                             void *result, Size allocated_size);
      27              : 
      28              : static const ExpandedObjectMethods EA_methods =
      29              : {
      30              :     EA_get_flat_size,
      31              :     EA_flatten_into
      32              : };
      33              : 
      34              : /* Other local functions */
      35              : static void copy_byval_expanded_array(ExpandedArrayHeader *eah,
      36              :                                       ExpandedArrayHeader *oldeah);
      37              : 
      38              : 
      39              : /*
      40              :  * expand_array: convert an array Datum into an expanded array
      41              :  *
      42              :  * The expanded object will be a child of parentcontext.
      43              :  *
      44              :  * Some callers can provide cache space to avoid repeated lookups of element
      45              :  * type data across calls; if so, pass a metacache pointer, making sure that
      46              :  * metacache->element_type is initialized to InvalidOid before first call.
      47              :  * If no cross-call caching is required, pass NULL for metacache.
      48              :  */
      49              : Datum
      50         8641 : expand_array(Datum arraydatum, MemoryContext parentcontext,
      51              :              ArrayMetaState *metacache)
      52              : {
      53              :     ArrayType  *array;
      54              :     ExpandedArrayHeader *eah;
      55              :     MemoryContext objcxt;
      56              :     MemoryContext oldcxt;
      57              :     ArrayMetaState fakecache;
      58              : 
      59              :     /*
      60              :      * Allocate private context for expanded object.  We start by assuming
      61              :      * that the array won't be very large; but if it does grow a lot, don't
      62              :      * constrain aset.c's large-context behavior.
      63              :      */
      64         8641 :     objcxt = AllocSetContextCreate(parentcontext,
      65              :                                    "expanded array",
      66              :                                    ALLOCSET_START_SMALL_SIZES);
      67              : 
      68              :     /* Set up expanded array header */
      69              :     eah = (ExpandedArrayHeader *)
      70         8641 :         MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));
      71              : 
      72         8641 :     EOH_init_header(&eah->hdr, &EA_methods, objcxt);
      73         8641 :     eah->ea_magic = EA_MAGIC;
      74              : 
      75              :     /* If the source is an expanded array, we may be able to optimize */
      76         8641 :     if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
      77              :     {
      78           21 :         ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
      79              : 
      80              :         Assert(oldeah->ea_magic == EA_MAGIC);
      81              : 
      82              :         /*
      83              :          * Update caller's cache if provided; we don't need it this time, but
      84              :          * next call might be for a non-expanded source array.  Furthermore,
      85              :          * if the caller didn't provide a cache area, use some local storage
      86              :          * to cache anyway, thereby avoiding a catalog lookup in the case
      87              :          * where we fall through to the flat-copy code path.
      88              :          */
      89           21 :         if (metacache == NULL)
      90            6 :             metacache = &fakecache;
      91           21 :         metacache->element_type = oldeah->element_type;
      92           21 :         metacache->typlen = oldeah->typlen;
      93           21 :         metacache->typbyval = oldeah->typbyval;
      94           21 :         metacache->typalign = oldeah->typalign;
      95              : 
      96              :         /*
      97              :          * If element type is pass-by-value and we have a Datum-array
      98              :          * representation, just copy the source's metadata and Datum/isnull
      99              :          * arrays.  The original flat array, if present at all, adds no
     100              :          * additional information so we need not copy it.
     101              :          */
     102           21 :         if (oldeah->typbyval && oldeah->dvalues != NULL)
     103              :         {
     104            6 :             copy_byval_expanded_array(eah, oldeah);
     105              :             /* return a R/W pointer to the expanded array */
     106            6 :             return EOHPGetRWDatum(&eah->hdr);
     107              :         }
     108              : 
     109              :         /*
     110              :          * Otherwise, either we have only a flat representation or the
     111              :          * elements are pass-by-reference.  In either case, the best thing
     112              :          * seems to be to copy the source as a flat representation and then
     113              :          * deconstruct that later if necessary.  For the pass-by-ref case, we
     114              :          * could perhaps save some cycles with custom code that generates the
     115              :          * deconstructed representation in parallel with copying the values,
     116              :          * but it would be a lot of extra code for fairly marginal gain.  So,
     117              :          * fall through into the flat-source code path.
     118              :          */
     119              :     }
     120              : 
     121              :     /*
     122              :      * Detoast and copy source array into private context, as a flat array.
     123              :      *
     124              :      * Note that this coding risks leaking some memory in the private context
     125              :      * if we have to fetch data from a TOAST table; however, experimentation
     126              :      * says that the leak is minimal.  Doing it this way saves a copy step,
     127              :      * which seems worthwhile, especially if the array is large enough to need
     128              :      * external storage.
     129              :      */
     130         8635 :     oldcxt = MemoryContextSwitchTo(objcxt);
     131         8635 :     array = DatumGetArrayTypePCopy(arraydatum);
     132         8635 :     MemoryContextSwitchTo(oldcxt);
     133              : 
     134         8635 :     eah->ndims = ARR_NDIM(array);
     135              :     /* note these pointers point into the fvalue header! */
     136         8635 :     eah->dims = ARR_DIMS(array);
     137         8635 :     eah->lbound = ARR_LBOUND(array);
     138              : 
     139              :     /* Save array's element-type data for possible use later */
     140         8635 :     eah->element_type = ARR_ELEMTYPE(array);
     141         8635 :     if (metacache && metacache->element_type == eah->element_type)
     142              :     {
     143              :         /* We have a valid cache of representational data */
     144          306 :         eah->typlen = metacache->typlen;
     145          306 :         eah->typbyval = metacache->typbyval;
     146          306 :         eah->typalign = metacache->typalign;
     147              :     }
     148              :     else
     149              :     {
     150              :         /* No, so look it up */
     151         8329 :         get_typlenbyvalalign(eah->element_type,
     152              :                              &eah->typlen,
     153              :                              &eah->typbyval,
     154              :                              &eah->typalign);
     155              :         /* Update cache if provided */
     156         8329 :         if (metacache)
     157              :         {
     158          531 :             metacache->element_type = eah->element_type;
     159          531 :             metacache->typlen = eah->typlen;
     160          531 :             metacache->typbyval = eah->typbyval;
     161          531 :             metacache->typalign = eah->typalign;
     162              :         }
     163              :     }
     164              : 
     165              :     /* we don't make a deconstructed representation now */
     166         8635 :     eah->dvalues = NULL;
     167         8635 :     eah->dnulls = NULL;
     168         8635 :     eah->dvalueslen = 0;
     169         8635 :     eah->nelems = 0;
     170         8635 :     eah->flat_size = 0;
     171              : 
     172              :     /* remember we have a flat representation */
     173         8635 :     eah->fvalue = array;
     174         8635 :     eah->fstartptr = ARR_DATA_PTR(array);
     175         8635 :     eah->fendptr = ((char *) array) + ARR_SIZE(array);
     176              : 
     177              :     /* return a R/W pointer to the expanded array */
     178         8635 :     return EOHPGetRWDatum(&eah->hdr);
     179              : }
     180              : 
     181              : /*
     182              :  * helper for expand_array(): copy pass-by-value Datum-array representation
     183              :  */
     184              : static void
     185            6 : copy_byval_expanded_array(ExpandedArrayHeader *eah,
     186              :                           ExpandedArrayHeader *oldeah)
     187              : {
     188            6 :     MemoryContext objcxt = eah->hdr.eoh_context;
     189            6 :     int         ndims = oldeah->ndims;
     190            6 :     int         dvalueslen = oldeah->dvalueslen;
     191              : 
     192              :     /* Copy array dimensionality information */
     193            6 :     eah->ndims = ndims;
     194              :     /* We can alloc both dimensionality arrays with one palloc */
     195            6 :     eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
     196            6 :     eah->lbound = eah->dims + ndims;
     197              :     /* .. but don't assume the source's arrays are contiguous */
     198            6 :     memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
     199            6 :     memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));
     200              : 
     201              :     /* Copy element-type data */
     202            6 :     eah->element_type = oldeah->element_type;
     203            6 :     eah->typlen = oldeah->typlen;
     204            6 :     eah->typbyval = oldeah->typbyval;
     205            6 :     eah->typalign = oldeah->typalign;
     206              : 
     207              :     /* Copy the deconstructed representation */
     208            6 :     eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
     209              :                                                 dvalueslen * sizeof(Datum));
     210            6 :     memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
     211            6 :     if (oldeah->dnulls)
     212              :     {
     213            0 :         eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
     214              :                                                   dvalueslen * sizeof(bool));
     215            0 :         memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
     216              :     }
     217              :     else
     218            6 :         eah->dnulls = NULL;
     219            6 :     eah->dvalueslen = dvalueslen;
     220            6 :     eah->nelems = oldeah->nelems;
     221            6 :     eah->flat_size = oldeah->flat_size;
     222              : 
     223              :     /* we don't make a flat representation */
     224            6 :     eah->fvalue = NULL;
     225            6 :     eah->fstartptr = NULL;
     226            6 :     eah->fendptr = NULL;
     227            6 : }
     228              : 
     229              : /*
     230              :  * get_flat_size method for expanded arrays
     231              :  */
     232              : static Size
     233         6211 : EA_get_flat_size(ExpandedObjectHeader *eohptr)
     234              : {
     235         6211 :     ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
     236              :     int         nelems;
     237              :     int         ndims;
     238              :     Datum      *dvalues;
     239              :     bool       *dnulls;
     240              :     Size        nbytes;
     241              :     uint8       typalignby;
     242              :     int         i;
     243              : 
     244              :     Assert(eah->ea_magic == EA_MAGIC);
     245              : 
     246              :     /* Easy if we have a valid flattened value */
     247         6211 :     if (eah->fvalue)
     248         3263 :         return ARR_SIZE(eah->fvalue);
     249              : 
     250              :     /* If we have a cached size value, believe that */
     251         2948 :     if (eah->flat_size)
     252         2045 :         return eah->flat_size;
     253              : 
     254              :     /*
     255              :      * Compute space needed by examining dvalues/dnulls.  Note that the result
     256              :      * array will have a nulls bitmap if dnulls isn't NULL, even if the array
     257              :      * doesn't actually contain any nulls now.
     258              :      */
     259          903 :     nelems = eah->nelems;
     260          903 :     ndims = eah->ndims;
     261              :     Assert(nelems == ArrayGetNItems(ndims, eah->dims));
     262          903 :     dvalues = eah->dvalues;
     263          903 :     dnulls = eah->dnulls;
     264          903 :     nbytes = 0;
     265          903 :     typalignby = typalign_to_alignby(eah->typalign);
     266         3312 :     for (i = 0; i < nelems; i++)
     267              :     {
     268         2409 :         if (dnulls && dnulls[i])
     269            0 :             continue;
     270         2409 :         nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
     271         2409 :         nbytes = att_nominal_alignby(nbytes, typalignby);
     272              :         /* check for overflow of total request */
     273         2409 :         if (!AllocSizeIsValid(nbytes))
     274            0 :             ereport(ERROR,
     275              :                     (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     276              :                      errmsg("array size exceeds the maximum allowed (%zu)",
     277              :                             MaxAllocSize)));
     278              :     }
     279              : 
     280          903 :     if (dnulls)
     281            0 :         nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
     282              :     else
     283          903 :         nbytes += ARR_OVERHEAD_NONULLS(ndims);
     284              : 
     285              :     /* cache for next time */
     286          903 :     eah->flat_size = nbytes;
     287              : 
     288          903 :     return nbytes;
     289              : }
     290              : 
     291              : /*
     292              :  * flatten_into method for expanded arrays
     293              :  */
     294              : static void
     295         4817 : EA_flatten_into(ExpandedObjectHeader *eohptr,
     296              :                 void *result, Size allocated_size)
     297              : {
     298         4817 :     ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
     299         4817 :     ArrayType  *aresult = (ArrayType *) result;
     300              :     int         nelems;
     301              :     int         ndims;
     302              :     int32       dataoffset;
     303              : 
     304              :     Assert(eah->ea_magic == EA_MAGIC);
     305              : 
     306              :     /* Easy if we have a valid flattened value */
     307         4817 :     if (eah->fvalue)
     308              :     {
     309              :         Assert(allocated_size == ARR_SIZE(eah->fvalue));
     310         3235 :         memcpy(result, eah->fvalue, allocated_size);
     311         3235 :         return;
     312              :     }
     313              : 
     314              :     /* Else allocation should match previous get_flat_size result */
     315              :     Assert(allocated_size == eah->flat_size);
     316              : 
     317              :     /* Fill result array from dvalues/dnulls */
     318         1582 :     nelems = eah->nelems;
     319         1582 :     ndims = eah->ndims;
     320              : 
     321         1582 :     if (eah->dnulls)
     322            0 :         dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
     323              :     else
     324         1582 :         dataoffset = 0;         /* marker for no null bitmap */
     325              : 
     326              :     /* We must ensure that any pad space is zero-filled */
     327         1582 :     memset(aresult, 0, allocated_size);
     328              : 
     329         1582 :     SET_VARSIZE(aresult, allocated_size);
     330         1582 :     aresult->ndim = ndims;
     331         1582 :     aresult->dataoffset = dataoffset;
     332         1582 :     aresult->elemtype = eah->element_type;
     333         1582 :     memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
     334         1582 :     memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));
     335              : 
     336         1582 :     CopyArrayEls(aresult,
     337         1582 :                  eah->dvalues, eah->dnulls, nelems,
     338         1582 :                  eah->typlen, eah->typbyval, eah->typalign,
     339              :                  false);
     340              : }
     341              : 
     342              : /*
     343              :  * Argument fetching support code
     344              :  */
     345              : 
     346              : /*
     347              :  * DatumGetExpandedArray: get a writable expanded array from an input argument
     348              :  *
     349              :  * Caution: if the input is a read/write pointer, this returns the input
     350              :  * argument; so callers must be sure that their changes are "safe", that is
     351              :  * they cannot leave the array in a corrupt state.
     352              :  */
     353              : ExpandedArrayHeader *
     354         1833 : DatumGetExpandedArray(Datum d)
     355              : {
     356              :     /* If it's a writable expanded array already, just return it */
     357         1833 :     if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
     358              :     {
     359         1002 :         ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
     360              : 
     361              :         Assert(eah->ea_magic == EA_MAGIC);
     362         1002 :         return eah;
     363              :     }
     364              : 
     365              :     /* Else expand the hard way */
     366          831 :     d = expand_array(d, CurrentMemoryContext, NULL);
     367          831 :     return (ExpandedArrayHeader *) DatumGetEOHP(d);
     368              : }
     369              : 
     370              : /*
     371              :  * As above, when caller has the ability to cache element type info
     372              :  */
     373              : ExpandedArrayHeader *
     374          961 : DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
     375              : {
     376              :     /* If it's a writable expanded array already, just return it */
     377          961 :     if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
     378              :     {
     379          136 :         ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
     380              : 
     381              :         Assert(eah->ea_magic == EA_MAGIC);
     382              :         /* Update cache if provided */
     383          136 :         if (metacache)
     384              :         {
     385          136 :             metacache->element_type = eah->element_type;
     386          136 :             metacache->typlen = eah->typlen;
     387          136 :             metacache->typbyval = eah->typbyval;
     388          136 :             metacache->typalign = eah->typalign;
     389              :         }
     390          136 :         return eah;
     391              :     }
     392              : 
     393              :     /* Else expand using caller's cache if any */
     394          825 :     d = expand_array(d, CurrentMemoryContext, metacache);
     395          825 :     return (ExpandedArrayHeader *) DatumGetEOHP(d);
     396              : }
     397              : 
     398              : /*
     399              :  * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena
     400              :  * array.  The result must not be modified in-place.
     401              :  */
     402              : AnyArrayType *
     403      9446427 : DatumGetAnyArrayP(Datum d)
     404              : {
     405              :     ExpandedArrayHeader *eah;
     406              : 
     407              :     /*
     408              :      * If it's an expanded array (RW or RO), return the header pointer.
     409              :      */
     410      9446427 :     if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
     411              :     {
     412         9337 :         eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
     413              :         Assert(eah->ea_magic == EA_MAGIC);
     414         9337 :         return (AnyArrayType *) eah;
     415              :     }
     416              : 
     417              :     /* Else do regular detoasting as needed */
     418      9437090 :     return (AnyArrayType *) PG_DETOAST_DATUM(d);
     419              : }
     420              : 
     421              : /*
     422              :  * Create the Datum/isnull representation of an expanded array object
     423              :  * if we didn't do so previously
     424              :  */
     425              : void
     426         6893 : deconstruct_expanded_array(ExpandedArrayHeader *eah)
     427              : {
     428         6893 :     if (eah->dvalues == NULL)
     429              :     {
     430         5525 :         MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
     431              :         Datum      *dvalues;
     432              :         bool       *dnulls;
     433              :         int         nelems;
     434              : 
     435         5525 :         dnulls = NULL;
     436         5525 :         deconstruct_array(eah->fvalue,
     437              :                           eah->element_type,
     438         5525 :                           eah->typlen, eah->typbyval, eah->typalign,
     439              :                           &dvalues,
     440         5525 :                           ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
     441              :                           &nelems);
     442              : 
     443              :         /*
     444              :          * Update header only after successful completion of this step.  If
     445              :          * deconstruct_array fails partway through, worst consequence is some
     446              :          * leaked memory in the object's context.  If the caller fails at a
     447              :          * later point, that's fine, since the deconstructed representation is
     448              :          * valid anyhow.
     449              :          */
     450         5525 :         eah->dvalues = dvalues;
     451         5525 :         eah->dnulls = dnulls;
     452         5525 :         eah->dvalueslen = eah->nelems = nelems;
     453         5525 :         MemoryContextSwitchTo(oldcxt);
     454              :     }
     455         6893 : }
        

Generated by: LCOV version 2.0-1