/*------------------------------------------------------------------------- * * array_expanded.c * Basic functions for manipulating expanded arrays. * * Portions Copyright (c) 1996-2020, 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); } }