| /*------------------------------------------------------------------------- |
| * |
| * array_expanded.c |
| * Basic functions for manipulating expanded arrays. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/utils/adt/array_expanded.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/tupmacs.h" |
| #include "utils/array.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| |
| |
| /* "Methods" required for an expanded object */ |
| static Size EA_get_flat_size(ExpandedObjectHeader *eohptr); |
| static void EA_flatten_into(ExpandedObjectHeader *eohptr, |
| void *result, Size allocated_size); |
| |
| static const ExpandedObjectMethods EA_methods = |
| { |
| EA_get_flat_size, |
| EA_flatten_into |
| }; |
| |
| /* Other local functions */ |
| static void copy_byval_expanded_array(ExpandedArrayHeader *eah, |
| ExpandedArrayHeader *oldeah); |
| |
| |
| /* |
| * expand_array: convert an array Datum into an expanded array |
| * |
| * The expanded object will be a child of parentcontext. |
| * |
| * Some callers can provide cache space to avoid repeated lookups of element |
| * type data across calls; if so, pass a metacache pointer, making sure that |
| * metacache->element_type is initialized to InvalidOid before first call. |
| * If no cross-call caching is required, pass NULL for metacache. |
| */ |
| Datum |
| expand_array(Datum arraydatum, MemoryContext parentcontext, |
| ArrayMetaState *metacache) |
| { |
| ArrayType *array; |
| ExpandedArrayHeader *eah; |
| MemoryContext objcxt; |
| MemoryContext oldcxt; |
| ArrayMetaState fakecache; |
| |
| /* |
| * Allocate private context for expanded object. We start by assuming |
| * that the array won't be very large; but if it does grow a lot, don't |
| * constrain aset.c's large-context behavior. |
| */ |
| objcxt = AllocSetContextCreate(parentcontext, |
| "expanded array", |
| ALLOCSET_START_SMALL_SIZES); |
| |
| /* Set up expanded array header */ |
| eah = (ExpandedArrayHeader *) |
| MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader)); |
| |
| EOH_init_header(&eah->hdr, &EA_methods, objcxt); |
| eah->ea_magic = EA_MAGIC; |
| |
| /* If the source is an expanded array, we may be able to optimize */ |
| if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) |
| { |
| ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum); |
| |
| Assert(oldeah->ea_magic == EA_MAGIC); |
| |
| /* |
| * Update caller's cache if provided; we don't need it this time, but |
| * next call might be for a non-expanded source array. Furthermore, |
| * if the caller didn't provide a cache area, use some local storage |
| * to cache anyway, thereby avoiding a catalog lookup in the case |
| * where we fall through to the flat-copy code path. |
| */ |
| if (metacache == NULL) |
| metacache = &fakecache; |
| metacache->element_type = oldeah->element_type; |
| metacache->typlen = oldeah->typlen; |
| metacache->typbyval = oldeah->typbyval; |
| metacache->typalign = oldeah->typalign; |
| |
| /* |
| * If element type is pass-by-value and we have a Datum-array |
| * representation, just copy the source's metadata and Datum/isnull |
| * arrays. The original flat array, if present at all, adds no |
| * additional information so we need not copy it. |
| */ |
| if (oldeah->typbyval && oldeah->dvalues != NULL) |
| { |
| copy_byval_expanded_array(eah, oldeah); |
| /* return a R/W pointer to the expanded array */ |
| return EOHPGetRWDatum(&eah->hdr); |
| } |
| |
| /* |
| * Otherwise, either we have only a flat representation or the |
| * elements are pass-by-reference. In either case, the best thing |
| * seems to be to copy the source as a flat representation and then |
| * deconstruct that later if necessary. For the pass-by-ref case, we |
| * could perhaps save some cycles with custom code that generates the |
| * deconstructed representation in parallel with copying the values, |
| * but it would be a lot of extra code for fairly marginal gain. So, |
| * fall through into the flat-source code path. |
| */ |
| } |
| |
| /* |
| * Detoast and copy source array into private context, as a flat array. |
| * |
| * Note that this coding risks leaking some memory in the private context |
| * if we have to fetch data from a TOAST table; however, experimentation |
| * says that the leak is minimal. Doing it this way saves a copy step, |
| * which seems worthwhile, especially if the array is large enough to need |
| * external storage. |
| */ |
| oldcxt = MemoryContextSwitchTo(objcxt); |
| array = DatumGetArrayTypePCopy(arraydatum); |
| MemoryContextSwitchTo(oldcxt); |
| |
| eah->ndims = ARR_NDIM(array); |
| /* note these pointers point into the fvalue header! */ |
| eah->dims = ARR_DIMS(array); |
| eah->lbound = ARR_LBOUND(array); |
| |
| /* Save array's element-type data for possible use later */ |
| eah->element_type = ARR_ELEMTYPE(array); |
| if (metacache && metacache->element_type == eah->element_type) |
| { |
| /* We have a valid cache of representational data */ |
| eah->typlen = metacache->typlen; |
| eah->typbyval = metacache->typbyval; |
| eah->typalign = metacache->typalign; |
| } |
| else |
| { |
| /* No, so look it up */ |
| get_typlenbyvalalign(eah->element_type, |
| &eah->typlen, |
| &eah->typbyval, |
| &eah->typalign); |
| /* Update cache if provided */ |
| if (metacache) |
| { |
| metacache->element_type = eah->element_type; |
| metacache->typlen = eah->typlen; |
| metacache->typbyval = eah->typbyval; |
| metacache->typalign = eah->typalign; |
| } |
| } |
| |
| /* we don't make a deconstructed representation now */ |
| eah->dvalues = NULL; |
| eah->dnulls = NULL; |
| eah->dvalueslen = 0; |
| eah->nelems = 0; |
| eah->flat_size = 0; |
| |
| /* remember we have a flat representation */ |
| eah->fvalue = array; |
| eah->fstartptr = ARR_DATA_PTR(array); |
| eah->fendptr = ((char *) array) + ARR_SIZE(array); |
| |
| /* return a R/W pointer to the expanded array */ |
| return EOHPGetRWDatum(&eah->hdr); |
| } |
| |
| /* |
| * helper for expand_array(): copy pass-by-value Datum-array representation |
| */ |
| static void |
| copy_byval_expanded_array(ExpandedArrayHeader *eah, |
| ExpandedArrayHeader *oldeah) |
| { |
| MemoryContext objcxt = eah->hdr.eoh_context; |
| int ndims = oldeah->ndims; |
| int dvalueslen = oldeah->dvalueslen; |
| |
| /* Copy array dimensionality information */ |
| eah->ndims = ndims; |
| /* We can alloc both dimensionality arrays with one palloc */ |
| eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int)); |
| eah->lbound = eah->dims + ndims; |
| /* .. but don't assume the source's arrays are contiguous */ |
| memcpy(eah->dims, oldeah->dims, ndims * sizeof(int)); |
| memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int)); |
| |
| /* Copy element-type data */ |
| eah->element_type = oldeah->element_type; |
| eah->typlen = oldeah->typlen; |
| eah->typbyval = oldeah->typbyval; |
| eah->typalign = oldeah->typalign; |
| |
| /* Copy the deconstructed representation */ |
| eah->dvalues = (Datum *) MemoryContextAlloc(objcxt, |
| dvalueslen * sizeof(Datum)); |
| memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum)); |
| if (oldeah->dnulls) |
| { |
| eah->dnulls = (bool *) MemoryContextAlloc(objcxt, |
| dvalueslen * sizeof(bool)); |
| memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool)); |
| } |
| else |
| eah->dnulls = NULL; |
| eah->dvalueslen = dvalueslen; |
| eah->nelems = oldeah->nelems; |
| eah->flat_size = oldeah->flat_size; |
| |
| /* we don't make a flat representation */ |
| eah->fvalue = NULL; |
| eah->fstartptr = NULL; |
| eah->fendptr = NULL; |
| } |
| |
| /* |
| * get_flat_size method for expanded arrays |
| */ |
| static Size |
| EA_get_flat_size(ExpandedObjectHeader *eohptr) |
| { |
| ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr; |
| int nelems; |
| int ndims; |
| Datum *dvalues; |
| bool *dnulls; |
| Size nbytes; |
| int i; |
| |
| Assert(eah->ea_magic == EA_MAGIC); |
| |
| /* Easy if we have a valid flattened value */ |
| if (eah->fvalue) |
| return ARR_SIZE(eah->fvalue); |
| |
| /* If we have a cached size value, believe that */ |
| if (eah->flat_size) |
| return eah->flat_size; |
| |
| /* |
| * Compute space needed by examining dvalues/dnulls. Note that the result |
| * array will have a nulls bitmap if dnulls isn't NULL, even if the array |
| * doesn't actually contain any nulls now. |
| */ |
| nelems = eah->nelems; |
| ndims = eah->ndims; |
| Assert(nelems == ArrayGetNItems(ndims, eah->dims)); |
| dvalues = eah->dvalues; |
| dnulls = eah->dnulls; |
| nbytes = 0; |
| for (i = 0; i < nelems; i++) |
| { |
| if (dnulls && dnulls[i]) |
| continue; |
| nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]); |
| nbytes = att_align_nominal(nbytes, eah->typalign); |
| /* check for overflow of total request */ |
| if (!AllocSizeIsValid(nbytes)) |
| ereport(ERROR, |
| (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
| errmsg("array size exceeds the maximum allowed (%d)", |
| (int) MaxAllocSize))); |
| } |
| |
| if (dnulls) |
| nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems); |
| else |
| nbytes += ARR_OVERHEAD_NONULLS(ndims); |
| |
| /* cache for next time */ |
| eah->flat_size = nbytes; |
| |
| return nbytes; |
| } |
| |
| /* |
| * flatten_into method for expanded arrays |
| */ |
| static void |
| EA_flatten_into(ExpandedObjectHeader *eohptr, |
| void *result, Size allocated_size) |
| { |
| ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr; |
| ArrayType *aresult = (ArrayType *) result; |
| int nelems; |
| int ndims; |
| int32 dataoffset; |
| |
| Assert(eah->ea_magic == EA_MAGIC); |
| |
| /* Easy if we have a valid flattened value */ |
| if (eah->fvalue) |
| { |
| Assert(allocated_size == ARR_SIZE(eah->fvalue)); |
| memcpy(result, eah->fvalue, allocated_size); |
| return; |
| } |
| |
| /* Else allocation should match previous get_flat_size result */ |
| Assert(allocated_size == eah->flat_size); |
| |
| /* Fill result array from dvalues/dnulls */ |
| nelems = eah->nelems; |
| ndims = eah->ndims; |
| |
| if (eah->dnulls) |
| dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems); |
| else |
| dataoffset = 0; /* marker for no null bitmap */ |
| |
| /* We must ensure that any pad space is zero-filled */ |
| memset(aresult, 0, allocated_size); |
| |
| SET_VARSIZE(aresult, allocated_size); |
| aresult->ndim = ndims; |
| aresult->dataoffset = dataoffset; |
| aresult->elemtype = eah->element_type; |
| memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int)); |
| memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int)); |
| |
| CopyArrayEls(aresult, |
| eah->dvalues, eah->dnulls, nelems, |
| eah->typlen, eah->typbyval, eah->typalign, |
| false); |
| } |
| |
| /* |
| * Argument fetching support code |
| */ |
| |
| /* |
| * DatumGetExpandedArray: get a writable expanded array from an input argument |
| * |
| * Caution: if the input is a read/write pointer, this returns the input |
| * argument; so callers must be sure that their changes are "safe", that is |
| * they cannot leave the array in a corrupt state. |
| */ |
| ExpandedArrayHeader * |
| DatumGetExpandedArray(Datum d) |
| { |
| /* If it's a writable expanded array already, just return it */ |
| if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) |
| { |
| ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); |
| |
| Assert(eah->ea_magic == EA_MAGIC); |
| return eah; |
| } |
| |
| /* Else expand the hard way */ |
| d = expand_array(d, CurrentMemoryContext, NULL); |
| return (ExpandedArrayHeader *) DatumGetEOHP(d); |
| } |
| |
| /* |
| * As above, when caller has the ability to cache element type info |
| */ |
| ExpandedArrayHeader * |
| DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache) |
| { |
| /* If it's a writable expanded array already, just return it */ |
| if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) |
| { |
| ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); |
| |
| Assert(eah->ea_magic == EA_MAGIC); |
| /* Update cache if provided */ |
| if (metacache) |
| { |
| metacache->element_type = eah->element_type; |
| metacache->typlen = eah->typlen; |
| metacache->typbyval = eah->typbyval; |
| metacache->typalign = eah->typalign; |
| } |
| return eah; |
| } |
| |
| /* Else expand using caller's cache if any */ |
| d = expand_array(d, CurrentMemoryContext, metacache); |
| return (ExpandedArrayHeader *) DatumGetEOHP(d); |
| } |
| |
| /* |
| * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena |
| * array. The result must not be modified in-place. |
| */ |
| AnyArrayType * |
| DatumGetAnyArrayP(Datum d) |
| { |
| ExpandedArrayHeader *eah; |
| |
| /* |
| * If it's an expanded array (RW or RO), return the header pointer. |
| */ |
| if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d))) |
| { |
| eah = (ExpandedArrayHeader *) DatumGetEOHP(d); |
| Assert(eah->ea_magic == EA_MAGIC); |
| return (AnyArrayType *) eah; |
| } |
| |
| /* Else do regular detoasting as needed */ |
| return (AnyArrayType *) PG_DETOAST_DATUM(d); |
| } |
| |
| /* |
| * Create the Datum/isnull representation of an expanded array object |
| * if we didn't do so previously |
| */ |
| void |
| deconstruct_expanded_array(ExpandedArrayHeader *eah) |
| { |
| if (eah->dvalues == NULL) |
| { |
| MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context); |
| Datum *dvalues; |
| bool *dnulls; |
| int nelems; |
| |
| dnulls = NULL; |
| deconstruct_array(eah->fvalue, |
| eah->element_type, |
| eah->typlen, eah->typbyval, eah->typalign, |
| &dvalues, |
| ARR_HASNULL(eah->fvalue) ? &dnulls : NULL, |
| &nelems); |
| |
| /* |
| * Update header only after successful completion of this step. If |
| * deconstruct_array fails partway through, worst consequence is some |
| * leaked memory in the object's context. If the caller fails at a |
| * later point, that's fine, since the deconstructed representation is |
| * valid anyhow. |
| */ |
| eah->dvalues = dvalues; |
| eah->dnulls = dnulls; |
| eah->dvalueslen = eah->nelems = nelems; |
| MemoryContextSwitchTo(oldcxt); |
| } |
| } |