<acronym>TOAST</> pointers can point to data that is not on disk, but is
elsewhere in the memory of the current server process. Such pointers
obviously cannot be long-lived, but they are nonetheless useful. There
-is currently just one sub-case:
-pointers to <firstterm>indirect</> data.
+are currently two sub-cases:
+pointers to <firstterm>indirect</> data and
+pointers to <firstterm>expanded</> data.
</para>
<para>
and there is no infrastructure to help with this.
</para>
+<para>
+Expanded <acronym>TOAST</> pointers are useful for complex data types
+whose on-disk representation is not especially suited for computational
+purposes. As an example, the standard varlena representation of a
+<productname>PostgreSQL</> array includes dimensionality information, a
+nulls bitmap if there are any null elements, then the values of all the
+elements in order. When the element type itself is variable-length, the
+only way to find the <replaceable>N</>'th element is to scan through all the
+preceding elements. This representation is appropriate for on-disk storage
+because of its compactness, but for computations with the array it's much
+nicer to have an <quote>expanded</> or <quote>deconstructed</>
+representation in which all the element starting locations have been
+identified. The <acronym>TOAST</> pointer mechanism supports this need by
+allowing a pass-by-reference Datum to point to either a standard varlena
+value (the on-disk representation) or a <acronym>TOAST</> pointer that
+points to an expanded representation somewhere in memory. The details of
+this expanded representation are up to the data type, though it must have
+a standard header and meet the other API requirements given
+in <filename>src/include/utils/expandeddatum.h</>. C-level functions
+working with the data type can choose to handle either representation.
+Functions that do not know about the expanded representation, but simply
+apply <function>PG_DETOAST_DATUM</> to their inputs, will automatically
+receive the traditional varlena representation; so support for an expanded
+representation can be introduced incrementally, one function at a time.
+</para>
+
+<para>
+<acronym>TOAST</> pointers to expanded values are further broken down
+into <firstterm>read-write</> and <firstterm>read-only</> pointers.
+The pointed-to representation is the same either way, but a function that
+receives a read-write pointer is allowed to modify the referenced value
+in-place, whereas one that receives a read-only pointer must not; it must
+first create a copy if it wants to make a modified version of the value.
+This distinction and some associated conventions make it possible to avoid
+unnecessary copying of expanded values during query execution.
+</para>
+
<para>
For all types of in-memory <acronym>TOAST</> pointer, the <acronym>TOAST</>
management code ensures that no such pointer datum can accidentally get
</para>
</note>
+ <para>
+ Another feature that's enabled by <acronym>TOAST</> support is the
+ possibility of having an <firstterm>expanded</> in-memory data
+ representation that is more convenient to work with than the format that
+ is stored on disk. The regular or <quote>flat</> varlena storage format
+ is ultimately just a blob of bytes; it cannot for example contain
+ pointers, since it may get copied to other locations in memory.
+ For complex data types, the flat format may be quite expensive to work
+ with, so <productname>PostgreSQL</> provides a way to <quote>expand</>
+ the flat format into a representation that is more suited to computation,
+ and then pass that format in-memory between functions of the data type.
+ </para>
+
+ <para>
+ To use expanded storage, a data type must define an expanded format that
+ follows the rules given in <filename>src/include/utils/expandeddatum.h</>,
+ and provide functions to <quote>expand</> a flat varlena value into
+ expanded format and <quote>flatten</> the expanded format back to the
+ regular varlena representation. Then ensure that all C functions for
+ the data type can accept either representation, possibly by converting
+ one into the other immediately upon receipt. This does not require fixing
+ all existing functions for the data type at once, because the standard
+ <function>PG_DETOAST_DATUM</> macro is defined to convert expanded inputs
+ into regular flat format. Therefore, existing functions that work with
+ the flat varlena format will continue to work, though slightly
+ inefficiently, with expanded inputs; they need not be converted until and
+ unless better performance is important.
+ </para>
+
+ <para>
+ C functions that know how to work with an expanded representation
+ typically fall into two categories: those that can only handle expanded
+ format, and those that can handle either expanded or flat varlena inputs.
+ The former are easier to write but may be less efficient overall, because
+ converting a flat input to expanded form for use by a single function may
+ cost more than is saved by operating on the expanded format.
+ When only expanded format need be handled, conversion of flat inputs to
+ expanded form can be hidden inside an argument-fetching macro, so that
+ the function appears no more complex than one working with traditional
+ varlena input.
+ To handle both types of input, write an argument-fetching function that
+ will detoast external, short-header, and compressed varlena inputs, but
+ not expanded inputs. Such a function can be defined as returning a
+ pointer to a union of the flat varlena format and the expanded format.
+ Callers can use the <function>VARATT_IS_EXPANDED_HEADER()</> macro to
+ determine which format they received.
+ </para>
+
+ <para>
+ The <acronym>TOAST</> infrastructure not only allows regular varlena
+ values to be distinguished from expanded values, but also
+ distinguishes <quote>read-write</> and <quote>read-only</> pointers to
+ expanded values. C functions that only need to examine an expanded
+ value, or will only change it in safe and non-semantically-visible ways,
+ need not care which type of pointer they receive. C functions that
+ produce a modified version of an input value are allowed to modify an
+ expanded input value in-place if they receive a read-write pointer, but
+ must not modify the input if they receive a read-only pointer; in that
+ case they have to copy the value first, producing a new value to modify.
+ A C function that has constructed a new expanded value should always
+ return a read-write pointer to it. Also, a C function that is modifying
+ a read-write expanded value in-place should take care to leave the value
+ in a sane state if it fails partway through.
+ </para>
+
+ <para>
+ For examples of working with expanded values, see the standard array
+ infrastructure, particularly
+ <filename>src/backend/utils/adt/array_expanded.c</>.
+ </para>
+
</sect2>
</sect1>
#include "access/sysattr.h"
#include "access/tuptoaster.h"
#include "executor/tuptable.h"
+#include "utils/expandeddatum.h"
/* Does att's datatype allow packing into the 1-byte-header varlena format? */
for (i = 0; i < numberOfAttributes; i++)
{
Datum val;
+ Form_pg_attribute atti;
if (isnull[i])
continue;
val = values[i];
+ atti = att[i];
- if (ATT_IS_PACKABLE(att[i]) &&
+ if (ATT_IS_PACKABLE(atti) &&
VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
{
/*
*/
data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val));
}
+ else if (atti->attlen == -1 &&
+ VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val)))
+ {
+ /*
+ * we want to flatten the expanded value so that the constructed
+ * tuple doesn't depend on it
+ */
+ data_length = att_align_nominal(data_length, atti->attalign);
+ data_length += EOH_get_flat_size(DatumGetEOHP(val));
+ }
else
{
- data_length = att_align_datum(data_length, att[i]->attalign,
- att[i]->attlen, val);
- data_length = att_addlength_datum(data_length, att[i]->attlen,
+ data_length = att_align_datum(data_length, atti->attalign,
+ atti->attlen, val);
+ data_length = att_addlength_datum(data_length, atti->attlen,
val);
}
}
*infomask |= HEAP_HASVARWIDTH;
if (VARATT_IS_EXTERNAL(val))
{
- *infomask |= HEAP_HASEXTERNAL;
- /* no alignment, since it's short by definition */
- data_length = VARSIZE_EXTERNAL(val);
- memcpy(data, val, data_length);
+ if (VARATT_IS_EXTERNAL_EXPANDED(val))
+ {
+ /*
+ * we want to flatten the expanded value so that the
+ * constructed tuple doesn't depend on it
+ */
+ ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]);
+
+ data = (char *) att_align_nominal(data,
+ att[i]->attalign);
+ data_length = EOH_get_flat_size(eoh);
+ EOH_flatten_into(eoh, data, data_length);
+ }
+ else
+ {
+ *infomask |= HEAP_HASEXTERNAL;
+ /* no alignment, since it's short by definition */
+ data_length = VARSIZE_EXTERNAL(val);
+ memcpy(data, val, data_length);
+ }
}
else if (VARATT_IS_SHORT(val))
{
#include "catalog/catalog.h"
#include "common/pg_lzcompress.h"
#include "miscadmin.h"
+#include "utils/expandeddatum.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/typcache.h"
result = (struct varlena *) palloc(VARSIZE_ANY(attr));
memcpy(result, attr, VARSIZE_ANY(attr));
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+ {
+ /*
+ * This is an expanded-object pointer --- get flat format
+ */
+ ExpandedObjectHeader *eoh;
+ Size resultsize;
+
+ eoh = DatumGetEOHP(PointerGetDatum(attr));
+ resultsize = EOH_get_flat_size(eoh);
+ result = (struct varlena *) palloc(resultsize);
+ EOH_flatten_into(eoh, (void *) result, resultsize);
+ }
else
{
/*
attr = result;
}
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+ {
+ /*
+ * This is an expanded-object pointer --- get flat format
+ */
+ attr = heap_tuple_fetch_attr(attr);
+ /* flatteners are not allowed to produce compressed/short output */
+ Assert(!VARATT_IS_EXTENDED(attr));
+ }
else if (VARATT_IS_COMPRESSED(attr))
{
/*
return heap_tuple_untoast_attr_slice(redirect.pointer,
sliceoffset, slicelength);
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+ {
+ /* pass it off to heap_tuple_fetch_attr to flatten */
+ preslice = heap_tuple_fetch_attr(attr);
+ }
else
preslice = attr;
return toast_raw_datum_size(PointerGetDatum(toast_pointer.pointer));
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+ {
+ result = EOH_get_flat_size(DatumGetEOHP(value));
+ }
else if (VARATT_IS_COMPRESSED(attr))
{
/* here, va_rawsize is just the payload size */
return toast_datum_size(PointerGetDatum(toast_pointer.pointer));
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(attr))
+ {
+ result = EOH_get_flat_size(DatumGetEOHP(value));
+ }
else if (VARATT_IS_SHORT(attr))
{
result = VARSIZE_SHORT(attr);
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
Datum result;
- ArrayType *array;
FunctionCallInfoData locfcinfo;
result = ExecEvalExpr(astate->arg, econtext, isNull, isDone);
if (!OidIsValid(acoerce->elemfuncid))
{
/* Detoast input array if necessary, and copy in any case */
- array = DatumGetArrayTypePCopy(result);
+ ArrayType *array = DatumGetArrayTypePCopy(result);
+
ARR_ELEMTYPE(array) = astate->resultelemtype;
PG_RETURN_ARRAYTYPE_P(array);
}
- /* Detoast input array if necessary, but don't make a useless copy */
- array = DatumGetArrayTypeP(result);
-
/* Initialize function cache if first time through */
if (astate->elemfunc.fn_oid == InvalidOid)
{
*/
InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
InvalidOid, NULL, NULL);
- locfcinfo.arg[0] = PointerGetDatum(array);
+ locfcinfo.arg[0] = result;
locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
locfcinfo.argnull[0] = false;
locfcinfo.argnull[1] = false;
locfcinfo.argnull[2] = false;
- return array_map(&locfcinfo, ARR_ELEMTYPE(array), astate->resultelemtype,
- astate->amstate);
+ return array_map(&locfcinfo, astate->resultelemtype, astate->amstate);
}
/* ----------------------------------------------------------------
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "utils/builtins.h"
+#include "utils/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
return ExecStoreTuple(newTuple, dstslot, InvalidBuffer, true);
}
+/* --------------------------------
+ * ExecMakeSlotContentsReadOnly
+ * Mark any R/W expanded datums in the slot as read-only.
+ *
+ * This is needed when a slot that might contain R/W datum references is to be
+ * used as input for general expression evaluation. Since the expression(s)
+ * might contain more than one Var referencing the same R/W datum, we could
+ * get wrong answers if functions acting on those Vars thought they could
+ * modify the expanded value in-place.
+ *
+ * For notational reasons, we return the same slot passed in.
+ * --------------------------------
+ */
+TupleTableSlot *
+ExecMakeSlotContentsReadOnly(TupleTableSlot *slot)
+{
+ /*
+ * sanity checks
+ */
+ Assert(slot != NULL);
+ Assert(slot->tts_tupleDescriptor != NULL);
+ Assert(!slot->tts_isempty);
+
+ /*
+ * If the slot contains a physical tuple, it can't contain any expanded
+ * datums, because we flatten those when making a physical tuple. This
+ * might change later; but for now, we need do nothing unless the slot is
+ * virtual.
+ */
+ if (slot->tts_tuple == NULL)
+ {
+ Form_pg_attribute *att = slot->tts_tupleDescriptor->attrs;
+ int attnum;
+
+ for (attnum = 0; attnum < slot->tts_nvalid; attnum++)
+ {
+ slot->tts_values[attnum] =
+ MakeExpandedObjectReadOnly(slot->tts_values[attnum],
+ slot->tts_isnull[attnum],
+ att[attnum]->attlen);
+ }
+ }
+
+ return slot;
+}
+
/* ----------------------------------------------------------------
* convenience initialization routines
* We just return the subplan's result slot, rather than expending extra
* cycles for ExecCopySlot(). (Our own ScanTupleSlot is used only for
* EvalPlanQual rechecks.)
+ *
+ * We do need to mark the slot contents read-only to prevent interference
+ * between different functions reading the same datum from the slot. It's
+ * a bit hokey to do this to the subplan's slot, but should be safe
+ * enough.
*/
+ if (!TupIsNull(slot))
+ slot = ExecMakeSlotContentsReadOnly(slot);
+
return slot;
}
pfree(pointer);
}
+Datum
+SPI_datumTransfer(Datum value, bool typByVal, int typLen)
+{
+ MemoryContext oldcxt = NULL;
+ Datum result;
+
+ if (_SPI_curid + 1 == _SPI_connected) /* connected */
+ {
+ if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
+ elog(ERROR, "SPI stack corrupted");
+ oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+ }
+
+ result = datumTransfer(value, typByVal, typLen);
+
+ if (oldcxt)
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
void
SPI_freetuple(HeapTuple tuple)
{
endif
# keep this list arranged alphabetically or it gets to be a mess
-OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
- array_userfuncs.o arrayutils.o ascii.o bool.o \
- cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
- encode.o enum.o float.o format_type.o formatting.o genfile.o \
+OBJS = acl.o arrayfuncs.o array_expanded.o array_selfuncs.o \
+ array_typanalyze.o array_userfuncs.o arrayutils.o ascii.o \
+ bool.o cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
+ encode.o enum.o expandeddatum.o \
+ float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
jsonfuncs.o like.o lockfuncs.o mac.o misc.o nabstime.o name.o \
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * array_expanded.c
+ * Basic functions for manipulating expanded arrays.
+ *
+ * Portions Copyright (c) 1996-2015, 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_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /* 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);
+}
+
+/*
+ * DatumGetAnyArray: return either an expanded array or a detoasted varlena
+ * array. The result must not be modified in-place.
+ */
+AnyArrayType *
+DatumGetAnyArray(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);
+ }
+}
/*
* fetch_array_arg_replace_nulls
*
- * Fetch an array-valued argument; if it's null, construct an empty array
- * value of the proper data type. Also cache basic element type information
- * in fn_extra.
+ * Fetch an array-valued argument in expanded form; if it's null, construct an
+ * empty array value of the proper data type. Also cache basic element type
+ * information in fn_extra.
+ *
+ * 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.
*/
-static ArrayType *
+static ExpandedArrayHeader *
fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
{
- ArrayType *v;
+ ExpandedArrayHeader *eah;
Oid element_type;
ArrayMetaState *my_extra;
- /* First collect the array value */
+ /* If first time through, create datatype cache struct */
+ my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
+ if (my_extra == NULL)
+ {
+ my_extra = (ArrayMetaState *)
+ MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(ArrayMetaState));
+ my_extra->element_type = InvalidOid;
+ fcinfo->flinfo->fn_extra = my_extra;
+ }
+
+ /* Now collect the array value */
if (!PG_ARGISNULL(argno))
{
- v = PG_GETARG_ARRAYTYPE_P(argno);
- element_type = ARR_ELEMTYPE(v);
+ eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra);
}
else
{
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("input data type is not an array")));
- v = construct_empty_array(element_type);
- }
-
- /* Now cache required info, which might change from call to call */
- my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
- if (my_extra == NULL)
- {
- my_extra = (ArrayMetaState *)
- MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
- sizeof(ArrayMetaState));
- my_extra->element_type = InvalidOid;
- fcinfo->flinfo->fn_extra = my_extra;
- }
-
- if (my_extra->element_type != element_type)
- {
- get_typlenbyvalalign(element_type,
- &my_extra->typlen,
- &my_extra->typbyval,
- &my_extra->typalign);
- my_extra->element_type = element_type;
+ eah = construct_empty_expanded_array(element_type,
+ CurrentMemoryContext,
+ my_extra);
}
- return v;
+ return eah;
}
/*-----------------------------------------------------------------------------
Datum
array_append(PG_FUNCTION_ARGS)
{
- ArrayType *v;
+ ExpandedArrayHeader *eah;
Datum newelem;
bool isNull;
- ArrayType *result;
+ Datum result;
int *dimv,
*lb;
int indx;
ArrayMetaState *my_extra;
- v = fetch_array_arg_replace_nulls(fcinfo, 0);
+ eah = fetch_array_arg_replace_nulls(fcinfo, 0);
isNull = PG_ARGISNULL(1);
if (isNull)
newelem = (Datum) 0;
else
newelem = PG_GETARG_DATUM(1);
- if (ARR_NDIM(v) == 1)
+ if (eah->ndims == 1)
{
/* append newelem */
int ub;
- lb = ARR_LBOUND(v);
- dimv = ARR_DIMS(v);
+ lb = eah->lbound;
+ dimv = eah->dims;
ub = dimv[0] + lb[0] - 1;
indx = ub + 1;
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
- else if (ARR_NDIM(v) == 0)
+ else if (eah->ndims == 0)
indx = 1;
else
ereport(ERROR,
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
- result = array_set(v, 1, &indx, newelem, isNull,
+ result = array_set_element(EOHPGetRWDatum(&eah->hdr),
+ 1, &indx, newelem, isNull,
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_DATUM(result);
}
/*-----------------------------------------------------------------------------
Datum
array_prepend(PG_FUNCTION_ARGS)
{
- ArrayType *v;
+ ExpandedArrayHeader *eah;
Datum newelem;
bool isNull;
- ArrayType *result;
+ Datum result;
int *lb;
int indx;
+ int lb0;
ArrayMetaState *my_extra;
isNull = PG_ARGISNULL(0);
newelem = (Datum) 0;
else
newelem = PG_GETARG_DATUM(0);
- v = fetch_array_arg_replace_nulls(fcinfo, 1);
+ eah = fetch_array_arg_replace_nulls(fcinfo, 1);
- if (ARR_NDIM(v) == 1)
+ if (eah->ndims == 1)
{
/* prepend newelem */
- lb = ARR_LBOUND(v);
+ lb = eah->lbound;
indx = lb[0] - 1;
+ lb0 = lb[0];
/* overflow? */
if (indx > lb[0])
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
- else if (ARR_NDIM(v) == 0)
+ else if (eah->ndims == 0)
+ {
indx = 1;
+ lb0 = 1;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
- result = array_set(v, 1, &indx, newelem, isNull,
+ result = array_set_element(EOHPGetRWDatum(&eah->hdr),
+ 1, &indx, newelem, isNull,
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
/* Readjust result's LB to match the input's, as expected for prepend */
- if (ARR_NDIM(v) == 1)
- ARR_LBOUND(result)[0] = ARR_LBOUND(v)[0];
+ Assert(result == EOHPGetRWDatum(&eah->hdr));
+ if (eah->ndims == 1)
+ {
+ /* This is ok whether we've deconstructed or not */
+ eah->lbound[0] = lb0;
+ }
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_DATUM(result);
}
/*-----------------------------------------------------------------------------
#endif
#include <math.h>
+/* See arrayaccess.h */
+#define ARRAYACCESS_INCLUDE_DEFINITIONS
+
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "utils/array.h"
+#include "utils/arrayaccess.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
*/
#define ASSGN "="
+#define AARR_FREE_IF_COPY(array,n) \
+ do { \
+ if (!VARATT_IS_EXPANDED_HEADER(array)) \
+ PG_FREE_IF_COPY(array, n); \
+ } while (0)
+
typedef enum
{
ARRAY_NO_LEVEL,
int typlen, bool typbyval, char typalign,
Datum *values, bool *nulls,
bool *hasnulls, int32 *nbytes);
-static void CopyArrayEls(ArrayType *array,
- Datum *values, bool *nulls, int nitems,
- int typlen, bool typbyval, char typalign,
- bool freedata);
+static Datum array_get_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign,
+ bool *isNull);
+static Datum array_set_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ Datum dataValue, bool isNull,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign);
static bool array_get_isnull(const bits8 *nullbitmap, int offset);
static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
static Datum ArrayCast(char *value, bool byval, int len);
* the values are not toasted. (Doing it here doesn't work since the
* caller has already allocated space for the array...)
*/
-static void
+void
CopyArrayEls(ArrayType *array,
Datum *values,
bool *nulls,
Datum
array_out(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
- Oid element_type = ARR_ELEMTYPE(v);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
+ Oid element_type = AARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
*
* +2 allows for assignment operator + trailing null
*/
- bits8 *bitmap;
- int bitmask;
bool *needquotes,
needdims = false;
int nitems,
int ndim,
*dims,
*lb;
+ array_iter iter;
ArrayMetaState *my_extra;
/*
typalign = my_extra->typalign;
typdelim = my_extra->typdelim;
- ndim = ARR_NDIM(v);
- dims = ARR_DIMS(v);
- lb = ARR_LBOUND(v);
+ ndim = AARR_NDIM(v);
+ dims = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
nitems = ArrayGetNItems(ndim, dims);
if (nitems == 0)
needquotes = (bool *) palloc(nitems * sizeof(bool));
overall_length = 1; /* don't forget to count \0 at end. */
- p = ARR_DATA_PTR(v);
- bitmap = ARR_NULLBITMAP(v);
- bitmask = 1;
+ array_iter_setup(&iter, v);
for (i = 0; i < nitems; i++)
{
+ Datum itemvalue;
+ bool isnull;
bool needquote;
/* Get source element, checking for NULL */
- if (bitmap && (*bitmap & bitmask) == 0)
+ itemvalue = array_iter_next(&iter, &isnull, i,
+ typlen, typbyval, typalign);
+
+ if (isnull)
{
values[i] = pstrdup("NULL");
overall_length += 4;
}
else
{
- Datum itemvalue;
-
- itemvalue = fetch_att(p, typbyval, typlen);
values[i] = OutputFunctionCall(&my_extra->proc, itemvalue);
- p = att_addlength_pointer(p, typlen, p);
- p = (char *) att_align_nominal(p, typalign);
/* count data plus backslashes; detect chars needing quotes */
if (values[i][0] == '\0')
overall_length += 2;
/* and the comma */
overall_length += 1;
-
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
- }
}
/*
Datum
array_send(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
- Oid element_type = ARR_ELEMTYPE(v);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
+ Oid element_type = AARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
- char *p;
- bits8 *bitmap;
- int bitmask;
int nitems,
i;
int ndim,
- *dim;
+ *dim,
+ *lb;
StringInfoData buf;
+ array_iter iter;
ArrayMetaState *my_extra;
/*
typbyval = my_extra->typbyval;
typalign = my_extra->typalign;
- ndim = ARR_NDIM(v);
- dim = ARR_DIMS(v);
+ ndim = AARR_NDIM(v);
+ dim = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
nitems = ArrayGetNItems(ndim, dim);
pq_begintypsend(&buf);
/* Send the array header information */
pq_sendint(&buf, ndim, 4);
- pq_sendint(&buf, ARR_HASNULL(v) ? 1 : 0, 4);
+ pq_sendint(&buf, AARR_HASNULL(v) ? 1 : 0, 4);
pq_sendint(&buf, element_type, sizeof(Oid));
for (i = 0; i < ndim; i++)
{
- pq_sendint(&buf, ARR_DIMS(v)[i], 4);
- pq_sendint(&buf, ARR_LBOUND(v)[i], 4);
+ pq_sendint(&buf, dim[i], 4);
+ pq_sendint(&buf, lb[i], 4);
}
/* Send the array elements using the element's own sendproc */
- p = ARR_DATA_PTR(v);
- bitmap = ARR_NULLBITMAP(v);
- bitmask = 1;
+ array_iter_setup(&iter, v);
for (i = 0; i < nitems; i++)
{
+ Datum itemvalue;
+ bool isnull;
+
/* Get source element, checking for NULL */
- if (bitmap && (*bitmap & bitmask) == 0)
+ itemvalue = array_iter_next(&iter, &isnull, i,
+ typlen, typbyval, typalign);
+
+ if (isnull)
{
/* -1 length means a NULL */
pq_sendint(&buf, -1, 4);
}
else
{
- Datum itemvalue;
bytea *outputbytes;
- itemvalue = fetch_att(p, typbyval, typlen);
outputbytes = SendFunctionCall(&my_extra->proc, itemvalue);
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
pq_sendbytes(&buf, VARDATA(outputbytes),
VARSIZE(outputbytes) - VARHDRSZ);
pfree(outputbytes);
-
- p = att_addlength_pointer(p, typlen, p);
- p = (char *) att_align_nominal(p, typalign);
- }
-
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
}
}
Datum
array_ndims(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
- PG_RETURN_INT32(ARR_NDIM(v));
+ PG_RETURN_INT32(AARR_NDIM(v));
}
/*
Datum
array_dims(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
char *p;
int i;
int *dimv,
char buf[MAXDIM * 33 + 1];
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
- dimv = ARR_DIMS(v);
- lb = ARR_LBOUND(v);
+ dimv = AARR_DIMS(v);
+ lb = AARR_LBOUND(v);
p = buf;
- for (i = 0; i < ARR_NDIM(v); i++)
+ for (i = 0; i < AARR_NDIM(v); i++)
{
sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1);
p += strlen(p);
Datum
array_lower(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
int reqdim = PG_GETARG_INT32(1);
int *lb;
int result;
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
- if (reqdim <= 0 || reqdim > ARR_NDIM(v))
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
- lb = ARR_LBOUND(v);
+ lb = AARR_LBOUND(v);
result = lb[reqdim - 1];
PG_RETURN_INT32(result);
Datum
array_upper(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
int reqdim = PG_GETARG_INT32(1);
int *dimv,
*lb;
int result;
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
- if (reqdim <= 0 || reqdim > ARR_NDIM(v))
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
- lb = ARR_LBOUND(v);
- dimv = ARR_DIMS(v);
+ lb = AARR_LBOUND(v);
+ dimv = AARR_DIMS(v);
result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
Datum
array_length(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
int reqdim = PG_GETARG_INT32(1);
int *dimv;
int result;
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
- if (reqdim <= 0 || reqdim > ARR_NDIM(v))
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
- dimv = ARR_DIMS(v);
+ dimv = AARR_DIMS(v);
result = dimv[reqdim - 1];
Datum
array_cardinality(PG_FUNCTION_ARGS)
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
- PG_RETURN_INT32(ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)));
+ PG_RETURN_INT32(ArrayGetNItems(AARR_NDIM(v), AARR_DIMS(v)));
}
char elmalign,
bool *isNull)
{
- ArrayType *array;
int i,
ndim,
*dim,
arraydataptr = (char *) DatumGetPointer(arraydatum);
arraynullsptr = NULL;
}
+ else if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ {
+ /* expanded array: let's do this in a separate function */
+ return array_get_element_expanded(arraydatum,
+ nSubscripts,
+ indx,
+ arraytyplen,
+ elmlen,
+ elmbyval,
+ elmalign,
+ isNull);
+ }
else
{
- /* detoast input array if necessary */
- array = DatumGetArrayTypeP(arraydatum);
+ /* detoast array if necessary, producing normal varlena input */
+ ArrayType *array = DatumGetArrayTypeP(arraydatum);
ndim = ARR_NDIM(array);
dim = ARR_DIMS(array);
return ArrayCast(retptr, elmbyval, elmlen);
}
+/*
+ * Implementation of array_get_element() for an expanded array
+ */
+static Datum
+array_get_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign,
+ bool *isNull)
+{
+ ExpandedArrayHeader *eah;
+ int i,
+ ndim,
+ *dim,
+ *lb,
+ offset;
+ Datum *dvalues;
+ bool *dnulls;
+
+ eah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);
+ Assert(eah->ea_magic == EA_MAGIC);
+
+ /* sanity-check caller's info against object */
+ Assert(arraytyplen == -1);
+ Assert(elmlen == eah->typlen);
+ Assert(elmbyval == eah->typbyval);
+ Assert(elmalign == eah->typalign);
+
+ ndim = eah->ndims;
+ dim = eah->dims;
+ lb = eah->lbound;
+
+ /*
+ * Return NULL for invalid subscript
+ */
+ if (ndim != nSubscripts || ndim <= 0 || ndim > MAXDIM)
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] || indx[i] >= (dim[i] + lb[i]))
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+ }
+
+ /*
+ * Calculate the element number
+ */
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+
+ /*
+ * Deconstruct array if we didn't already. Note that we apply this even
+ * if the input is nominally read-only: it should be safe enough.
+ */
+ deconstruct_expanded_array(eah);
+
+ dvalues = eah->dvalues;
+ dnulls = eah->dnulls;
+
+ /*
+ * Check for NULL array element
+ */
+ if (dnulls && dnulls[offset])
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * OK, get the element. It's OK to return a pass-by-ref value as a
+ * pointer into the expanded array, for the same reason that regular
+ * array_get_element can return a pointer into flat arrays: the value is
+ * assumed not to change for as long as the Datum reference can exist.
+ */
+ *isNull = false;
+ return dvalues[offset];
+}
+
/*
* array_get_slice :
* This routine takes an array and a range of indices (upperIndex and
*
* Result:
* A new array is returned, just like the old except for the one
- * modified entry. The original array object is not changed.
+ * modified entry. The original array object is not changed,
+ * unless what is passed is a read-write reference to an expanded
+ * array object; in that case the expanded array is updated in-place.
*
* For one-dimensional arrays only, we allow the array to be extended
* by assigning to a position outside the existing subscript range; any
if (elmlen == -1 && !isNull)
dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue));
+ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ {
+ /* expanded array: let's do this in a separate function */
+ return array_set_element_expanded(arraydatum,
+ nSubscripts,
+ indx,
+ dataValue,
+ isNull,
+ arraytyplen,
+ elmlen,
+ elmbyval,
+ elmalign);
+ }
+
/* detoast input array if necessary */
array = DatumGetArrayTypeP(arraydatum);
return PointerGetDatum(newarray);
}
+/*
+ * Implementation of array_set_element() for an expanded array
+ *
+ * Note: as with any operation on a read/write expanded object, we must
+ * take pains not to leave the object in a corrupt state if we fail partway
+ * through.
+ */
+static Datum
+array_set_element_expanded(Datum arraydatum,
+ int nSubscripts, int *indx,
+ Datum dataValue, bool isNull,
+ int arraytyplen,
+ int elmlen, bool elmbyval, char elmalign)
+{
+ ExpandedArrayHeader *eah;
+ Datum *dvalues;
+ bool *dnulls;
+ int i,
+ ndim,
+ dim[MAXDIM],
+ lb[MAXDIM],
+ offset;
+ bool dimschanged,
+ newhasnulls;
+ int addedbefore,
+ addedafter;
+ char *oldValue;
+
+ /* Convert to R/W object if not so already */
+ eah = DatumGetExpandedArray(arraydatum);
+
+ /* Sanity-check caller's info against object; we don't use it otherwise */
+ Assert(arraytyplen == -1);
+ Assert(elmlen == eah->typlen);
+ Assert(elmbyval == eah->typbyval);
+ Assert(elmalign == eah->typalign);
+
+ /*
+ * Copy dimension info into local storage. This allows us to modify the
+ * dimensions if needed, while not messing up the expanded value if we
+ * fail partway through.
+ */
+ ndim = eah->ndims;
+ Assert(ndim >= 0 && ndim <= MAXDIM);
+ memcpy(dim, eah->dims, ndim * sizeof(int));
+ memcpy(lb, eah->lbound, ndim * sizeof(int));
+ dimschanged = false;
+
+ /*
+ * if number of dims is zero, i.e. an empty array, create an array with
+ * nSubscripts dimensions, and set the lower bounds to the supplied
+ * subscripts.
+ */
+ if (ndim == 0)
+ {
+ /*
+ * Allocate adequate space for new dimension info. This is harmless
+ * if we fail later.
+ */
+ Assert(nSubscripts > 0 && nSubscripts <= MAXDIM);
+ eah->dims = (int *) MemoryContextAllocZero(eah->hdr.eoh_context,
+ nSubscripts * sizeof(int));
+ eah->lbound = (int *) MemoryContextAllocZero(eah->hdr.eoh_context,
+ nSubscripts * sizeof(int));
+
+ /* Update local copies of dimension info */
+ ndim = nSubscripts;
+ for (i = 0; i < nSubscripts; i++)
+ {
+ dim[i] = 0;
+ lb[i] = indx[i];
+ }
+ dimschanged = true;
+ }
+ else if (ndim != nSubscripts)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("wrong number of array subscripts")));
+
+ /*
+ * Deconstruct array if we didn't already. (Someday maybe add a special
+ * case path for fixed-length, no-nulls cases, where we can overwrite an
+ * element in place without ever deconstructing. But today is not that
+ * day.)
+ */
+ deconstruct_expanded_array(eah);
+
+ /*
+ * Copy new element into array's context, if needed (we assume it's
+ * already detoasted, so no junk should be created). If we fail further
+ * down, this memory is leaked, but that's reasonably harmless.
+ */
+ if (!eah->typbyval && !isNull)
+ {
+ MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
+
+ dataValue = datumCopy(dataValue, false, eah->typlen);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ dvalues = eah->dvalues;
+ dnulls = eah->dnulls;
+
+ newhasnulls = ((dnulls != NULL) || isNull);
+ addedbefore = addedafter = 0;
+
+ /*
+ * Check subscripts (this logic matches original array_set_element)
+ */
+ if (ndim == 1)
+ {
+ if (indx[0] < lb[0])
+ {
+ addedbefore = lb[0] - indx[0];
+ dim[0] += addedbefore;
+ lb[0] = indx[0];
+ dimschanged = true;
+ if (addedbefore > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ if (indx[0] >= (dim[0] + lb[0]))
+ {
+ addedafter = indx[0] - (dim[0] + lb[0]) + 1;
+ dim[0] += addedafter;
+ dimschanged = true;
+ if (addedafter > 1)
+ newhasnulls = true; /* will insert nulls */
+ }
+ }
+ else
+ {
+ /*
+ * XXX currently we do not support extending multi-dimensional arrays
+ * during assignment
+ */
+ for (i = 0; i < ndim; i++)
+ {
+ if (indx[i] < lb[i] ||
+ indx[i] >= (dim[i] + lb[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("array subscript out of range")));
+ }
+ }
+
+ /* Now we can calculate linear offset of target item in array */
+ offset = ArrayGetOffset(nSubscripts, dim, lb, indx);
+
+ /* Physically enlarge existing dvalues/dnulls arrays if needed */
+ if (dim[0] > eah->dvalueslen)
+ {
+ /* We want some extra space if we're enlarging */
+ int newlen = dim[0] + dim[0] / 8;
+
+ newlen = Max(newlen, dim[0]); /* integer overflow guard */
+ eah->dvalues = dvalues = (Datum *)
+ repalloc(dvalues, newlen * sizeof(Datum));
+ if (dnulls)
+ eah->dnulls = dnulls = (bool *)
+ repalloc(dnulls, newlen * sizeof(bool));
+ eah->dvalueslen = newlen;
+ }
+
+ /*
+ * If we need a nulls bitmap and don't already have one, create it, being
+ * sure to mark all existing entries as not null.
+ */
+ if (newhasnulls && dnulls == NULL)
+ eah->dnulls = dnulls = (bool *)
+ MemoryContextAllocZero(eah->hdr.eoh_context,
+ eah->dvalueslen * sizeof(bool));
+
+ /*
+ * We now have all the needed space allocated, so we're ready to make
+ * irreversible changes. Be very wary of allowing failure below here.
+ */
+
+ /* Flattened value will no longer represent array accurately */
+ eah->fvalue = NULL;
+ /* And we don't know the flattened size either */
+ eah->flat_size = 0;
+
+ /* Update dimensionality info if needed */
+ if (dimschanged)
+ {
+ eah->ndims = ndim;
+ memcpy(eah->dims, dim, ndim * sizeof(int));
+ memcpy(eah->lbound, lb, ndim * sizeof(int));
+ }
+
+ /* Reposition items if needed, and fill addedbefore items with nulls */
+ if (addedbefore > 0)
+ {
+ memmove(dvalues + addedbefore, dvalues, eah->nelems * sizeof(Datum));
+ for (i = 0; i < addedbefore; i++)
+ dvalues[i] = (Datum) 0;
+ if (dnulls)
+ {
+ memmove(dnulls + addedbefore, dnulls, eah->nelems * sizeof(bool));
+ for (i = 0; i < addedbefore; i++)
+ dnulls[i] = true;
+ }
+ eah->nelems += addedbefore;
+ }
+
+ /* fill addedafter items with nulls */
+ if (addedafter > 0)
+ {
+ for (i = 0; i < addedafter; i++)
+ dvalues[eah->nelems + i] = (Datum) 0;
+ if (dnulls)
+ {
+ for (i = 0; i < addedafter; i++)
+ dnulls[eah->nelems + i] = true;
+ }
+ eah->nelems += addedafter;
+ }
+
+ /* Grab old element value for pfree'ing, if needed. */
+ if (!eah->typbyval && (dnulls == NULL || !dnulls[offset]))
+ oldValue = (char *) DatumGetPointer(dvalues[offset]);
+ else
+ oldValue = NULL;
+
+ /* And finally we can insert the new element. */
+ dvalues[offset] = dataValue;
+ if (dnulls)
+ dnulls[offset] = isNull;
+
+ /*
+ * Free old element if needed; this keeps repeated element replacements
+ * from bloating the array's storage. If the pfree somehow fails, it
+ * won't corrupt the array.
+ */
+ if (oldValue)
+ {
+ /* Don't try to pfree a part of the original flat array */
+ if (oldValue < eah->fstartptr || oldValue >= eah->fendptr)
+ pfree(oldValue);
+ }
+
+ /* Done, return standard TOAST pointer for object */
+ return EOHPGetRWDatum(&eah->hdr);
+}
+
/*
* array_set_slice :
* This routine sets the value of a range of array locations (specified
* the function fn(), and if nargs > 1 then argument positions after the
* first must be preset to the additional values to be passed. The
* first argument position initially holds the input array value.
- * * inpType: OID of element type of input array. This must be the same as,
- * or binary-compatible with, the first argument type of fn().
* * retType: OID of element type of output array. This must be the same as,
* or binary-compatible with, the result type of fn().
* * amstate: workspace for array_map. Must be zeroed by caller before
* the array are OK however.
*/
Datum
-array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType,
- ArrayMapState *amstate)
+array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
{
- ArrayType *v;
+ AnyArrayType *v;
ArrayType *result;
Datum *values;
bool *nulls;
- Datum elt;
int *dim;
int ndim;
int nitems;
int32 nbytes = 0;
int32 dataoffset;
bool hasnulls;
+ Oid inpType;
int inp_typlen;
bool inp_typbyval;
char inp_typalign;
int typlen;
bool typbyval;
char typalign;
- char *s;
- bits8 *bitmap;
- int bitmask;
+ array_iter iter;
ArrayMetaState *inp_extra;
ArrayMetaState *ret_extra;
elog(ERROR, "invalid nargs: %d", fcinfo->nargs);
if (PG_ARGISNULL(0))
elog(ERROR, "null input array");
- v = PG_GETARG_ARRAYTYPE_P(0);
-
- Assert(ARR_ELEMTYPE(v) == inpType);
+ v = PG_GETARG_ANY_ARRAY(0);
- ndim = ARR_NDIM(v);
- dim = ARR_DIMS(v);
+ inpType = AARR_ELEMTYPE(v);
+ ndim = AARR_NDIM(v);
+ dim = AARR_DIMS(v);
nitems = ArrayGetNItems(ndim, dim);
/* Check for empty array */
nulls = (bool *) palloc(nitems * sizeof(bool));
/* Loop over source data */
- s = ARR_DATA_PTR(v);
- bitmap = ARR_NULLBITMAP(v);
- bitmask = 1;
+ array_iter_setup(&iter, v);
hasnulls = false;
for (i = 0; i < nitems; i++)
bool callit = true;
/* Get source element, checking for NULL */
- if (bitmap && (*bitmap & bitmask) == 0)
- {
- fcinfo->argnull[0] = true;
- }
- else
- {
- elt = fetch_att(s, inp_typbyval, inp_typlen);
- s = att_addlength_datum(s, inp_typlen, elt);
- s = (char *) att_align_nominal(s, inp_typalign);
- fcinfo->arg[0] = elt;
- fcinfo->argnull[0] = false;
- }
+ fcinfo->arg[0] = array_iter_next(&iter, &fcinfo->argnull[0], i,
+ inp_typlen, inp_typbyval, inp_typalign);
/*
* Apply the given function to source elt and extra args.
errmsg("array size exceeds the maximum allowed (%d)",
(int) MaxAllocSize)));
}
-
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
- }
}
/* Allocate and initialize the result array */
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = retType;
- memcpy(ARR_DIMS(result), ARR_DIMS(v), 2 * ndim * sizeof(int));
+ memcpy(ARR_DIMS(result), AARR_DIMS(v), ndim * sizeof(int));
+ memcpy(ARR_LBOUND(result), AARR_LBOUND(v), ndim * sizeof(int));
/*
* Note: do not risk trying to pfree the results of the called function
return result;
}
+/*
+ * construct_empty_expanded_array: make an empty expanded array
+ * given only type information. (metacache can be NULL if not needed.)
+ */
+ExpandedArrayHeader *
+construct_empty_expanded_array(Oid element_type,
+ MemoryContext parentcontext,
+ ArrayMetaState *metacache)
+{
+ ArrayType *array = construct_empty_array(element_type);
+ Datum d;
+
+ d = expand_array(PointerGetDatum(array), parentcontext, metacache);
+ pfree(array);
+ return (ExpandedArrayHeader *) DatumGetEOHP(d);
+}
+
/*
* deconstruct_array --- simple method for extracting data from an array
*
Datum
array_eq(PG_FUNCTION_ARGS)
{
- ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
- ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
- int ndims1 = ARR_NDIM(array1);
- int ndims2 = ARR_NDIM(array2);
- int *dims1 = ARR_DIMS(array1);
- int *dims2 = ARR_DIMS(array2);
- Oid element_type = ARR_ELEMTYPE(array1);
+ int ndims1 = AARR_NDIM(array1);
+ int ndims2 = AARR_NDIM(array2);
+ int *dims1 = AARR_DIMS(array1);
+ int *dims2 = AARR_DIMS(array2);
+ int *lbs1 = AARR_LBOUND(array1);
+ int *lbs2 = AARR_LBOUND(array2);
+ Oid element_type = AARR_ELEMTYPE(array1);
bool result = true;
int nitems;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
- char *ptr1;
- char *ptr2;
- bits8 *bitmap1;
- bits8 *bitmap2;
- int bitmask;
+ array_iter it1;
+ array_iter it2;
int i;
FunctionCallInfoData locfcinfo;
- if (element_type != ARR_ELEMTYPE(array2))
+ if (element_type != AARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
/* fast path if the arrays do not have the same dimensionality */
if (ndims1 != ndims2 ||
- memcmp(dims1, dims2, 2 * ndims1 * sizeof(int)) != 0)
+ memcmp(dims1, dims2, ndims1 * sizeof(int)) != 0 ||
+ memcmp(lbs1, lbs2, ndims1 * sizeof(int)) != 0)
result = false;
else
{
/* Loop over source data */
nitems = ArrayGetNItems(ndims1, dims1);
- ptr1 = ARR_DATA_PTR(array1);
- ptr2 = ARR_DATA_PTR(array2);
- bitmap1 = ARR_NULLBITMAP(array1);
- bitmap2 = ARR_NULLBITMAP(array2);
- bitmask = 1; /* use same bitmask for both arrays */
+ array_iter_setup(&it1, array1);
+ array_iter_setup(&it2, array2);
for (i = 0; i < nitems; i++)
{
bool oprresult;
/* Get elements, checking for NULL */
- if (bitmap1 && (*bitmap1 & bitmask) == 0)
- {
- isnull1 = true;
- elt1 = (Datum) 0;
- }
- else
- {
- isnull1 = false;
- elt1 = fetch_att(ptr1, typbyval, typlen);
- ptr1 = att_addlength_pointer(ptr1, typlen, ptr1);
- ptr1 = (char *) att_align_nominal(ptr1, typalign);
- }
-
- if (bitmap2 && (*bitmap2 & bitmask) == 0)
- {
- isnull2 = true;
- elt2 = (Datum) 0;
- }
- else
- {
- isnull2 = false;
- elt2 = fetch_att(ptr2, typbyval, typlen);
- ptr2 = att_addlength_pointer(ptr2, typlen, ptr2);
- ptr2 = (char *) att_align_nominal(ptr2, typalign);
- }
-
- /* advance bitmap pointers if any */
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- if (bitmap1)
- bitmap1++;
- if (bitmap2)
- bitmap2++;
- bitmask = 1;
- }
+ elt1 = array_iter_next(&it1, &isnull1, i,
+ typlen, typbyval, typalign);
+ elt2 = array_iter_next(&it2, &isnull2, i,
+ typlen, typbyval, typalign);
/*
* We consider two NULLs equal; NULL and not-NULL are unequal.
}
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array1, 0);
- PG_FREE_IF_COPY(array2, 1);
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
static int
array_cmp(FunctionCallInfo fcinfo)
{
- ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
- ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
- int ndims1 = ARR_NDIM(array1);
- int ndims2 = ARR_NDIM(array2);
- int *dims1 = ARR_DIMS(array1);
- int *dims2 = ARR_DIMS(array2);
+ int ndims1 = AARR_NDIM(array1);
+ int ndims2 = AARR_NDIM(array2);
+ int *dims1 = AARR_DIMS(array1);
+ int *dims2 = AARR_DIMS(array2);
int nitems1 = ArrayGetNItems(ndims1, dims1);
int nitems2 = ArrayGetNItems(ndims2, dims2);
- Oid element_type = ARR_ELEMTYPE(array1);
+ Oid element_type = AARR_ELEMTYPE(array1);
int result = 0;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
int min_nitems;
- char *ptr1;
- char *ptr2;
- bits8 *bitmap1;
- bits8 *bitmap2;
- int bitmask;
+ array_iter it1;
+ array_iter it2;
int i;
FunctionCallInfoData locfcinfo;
- if (element_type != ARR_ELEMTYPE(array2))
+ if (element_type != AARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
/* Loop over source data */
min_nitems = Min(nitems1, nitems2);
- ptr1 = ARR_DATA_PTR(array1);
- ptr2 = ARR_DATA_PTR(array2);
- bitmap1 = ARR_NULLBITMAP(array1);
- bitmap2 = ARR_NULLBITMAP(array2);
- bitmask = 1; /* use same bitmask for both arrays */
+ array_iter_setup(&it1, array1);
+ array_iter_setup(&it2, array2);
for (i = 0; i < min_nitems; i++)
{
int32 cmpresult;
/* Get elements, checking for NULL */
- if (bitmap1 && (*bitmap1 & bitmask) == 0)
- {
- isnull1 = true;
- elt1 = (Datum) 0;
- }
- else
- {
- isnull1 = false;
- elt1 = fetch_att(ptr1, typbyval, typlen);
- ptr1 = att_addlength_pointer(ptr1, typlen, ptr1);
- ptr1 = (char *) att_align_nominal(ptr1, typalign);
- }
-
- if (bitmap2 && (*bitmap2 & bitmask) == 0)
- {
- isnull2 = true;
- elt2 = (Datum) 0;
- }
- else
- {
- isnull2 = false;
- elt2 = fetch_att(ptr2, typbyval, typlen);
- ptr2 = att_addlength_pointer(ptr2, typlen, ptr2);
- ptr2 = (char *) att_align_nominal(ptr2, typalign);
- }
-
- /* advance bitmap pointers if any */
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- if (bitmap1)
- bitmap1++;
- if (bitmap2)
- bitmap2++;
- bitmask = 1;
- }
+ elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign);
+ elt2 = array_iter_next(&it2, &isnull2, i, typlen, typbyval, typalign);
/*
* We consider two NULLs equal; NULL > not-NULL.
result = (ndims1 < ndims2) ? -1 : 1;
else
{
- /* this relies on LB array immediately following DIMS array */
- for (i = 0; i < ndims1 * 2; i++)
+ for (i = 0; i < ndims1; i++)
{
if (dims1[i] != dims2[i])
{
break;
}
}
+ if (result == 0)
+ {
+ int *lbound1 = AARR_LBOUND(array1);
+ int *lbound2 = AARR_LBOUND(array2);
+
+ for (i = 0; i < ndims1; i++)
+ {
+ if (lbound1[i] != lbound2[i])
+ {
+ result = (lbound1[i] < lbound2[i]) ? -1 : 1;
+ break;
+ }
+ }
+ }
}
}
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array1, 0);
- PG_FREE_IF_COPY(array2, 1);
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
return result;
}
Datum
hash_array(PG_FUNCTION_ARGS)
{
- ArrayType *array = PG_GETARG_ARRAYTYPE_P(0);
- int ndims = ARR_NDIM(array);
- int *dims = ARR_DIMS(array);
- Oid element_type = ARR_ELEMTYPE(array);
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ int ndims = AARR_NDIM(array);
+ int *dims = AARR_DIMS(array);
+ Oid element_type = AARR_ELEMTYPE(array);
uint32 result = 1;
int nitems;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
- char *ptr;
- bits8 *bitmap;
- int bitmask;
int i;
+ array_iter iter;
FunctionCallInfoData locfcinfo;
/*
/* Loop over source data */
nitems = ArrayGetNItems(ndims, dims);
- ptr = ARR_DATA_PTR(array);
- bitmap = ARR_NULLBITMAP(array);
- bitmask = 1;
+ array_iter_setup(&iter, array);
for (i = 0; i < nitems; i++)
{
+ Datum elt;
+ bool isnull;
uint32 elthash;
/* Get element, checking for NULL */
- if (bitmap && (*bitmap & bitmask) == 0)
+ elt = array_iter_next(&iter, &isnull, i, typlen, typbyval, typalign);
+
+ if (isnull)
{
/* Treat nulls as having hashvalue 0 */
elthash = 0;
}
else
{
- Datum elt;
-
- elt = fetch_att(ptr, typbyval, typlen);
- ptr = att_addlength_pointer(ptr, typlen, ptr);
- ptr = (char *) att_align_nominal(ptr, typalign);
-
/* Apply the hash function */
locfcinfo.arg[0] = elt;
locfcinfo.argnull[0] = false;
elthash = DatumGetUInt32(FunctionCallInvoke(&locfcinfo));
}
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
- }
-
/*
* Combine hash values of successive elements by multiplying the
* current value by 31 and adding on the new element's hash value.
}
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array, 0);
+ AARR_FREE_IF_COPY(array, 0);
PG_RETURN_UINT32(result);
}
* When matchall is false, return true if any members of array1 are in array2.
*/
static bool
-array_contain_compare(ArrayType *array1, ArrayType *array2, Oid collation,
+array_contain_compare(AnyArrayType *array1, AnyArrayType *array2, Oid collation,
bool matchall, void **fn_extra)
{
bool result = matchall;
- Oid element_type = ARR_ELEMTYPE(array1);
+ Oid element_type = AARR_ELEMTYPE(array1);
TypeCacheEntry *typentry;
int nelems1;
Datum *values2;
int typlen;
bool typbyval;
char typalign;
- char *ptr1;
- bits8 *bitmap1;
- int bitmask;
int i;
int j;
+ array_iter it1;
FunctionCallInfoData locfcinfo;
- if (element_type != ARR_ELEMTYPE(array2))
+ if (element_type != AARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
* worthwhile to use deconstruct_array on it. We scan array1 the hard way
* however, since we very likely won't need to look at all of it.
*/
- deconstruct_array(array2, element_type, typlen, typbyval, typalign,
- &values2, &nulls2, &nelems2);
+ if (VARATT_IS_EXPANDED_HEADER(array2))
+ {
+ /* This should be safe even if input is read-only */
+ deconstruct_expanded_array(&(array2->xpn));
+ values2 = array2->xpn.dvalues;
+ nulls2 = array2->xpn.dnulls;
+ nelems2 = array2->xpn.nelems;
+ }
+ else
+ deconstruct_array(&(array2->flt),
+ element_type, typlen, typbyval, typalign,
+ &values2, &nulls2, &nelems2);
/*
* Apply the comparison operator to each pair of array elements.
collation, NULL, NULL);
/* Loop over source data */
- nelems1 = ArrayGetNItems(ARR_NDIM(array1), ARR_DIMS(array1));
- ptr1 = ARR_DATA_PTR(array1);
- bitmap1 = ARR_NULLBITMAP(array1);
- bitmask = 1;
+ nelems1 = ArrayGetNItems(AARR_NDIM(array1), AARR_DIMS(array1));
+ array_iter_setup(&it1, array1);
for (i = 0; i < nelems1; i++)
{
bool isnull1;
/* Get element, checking for NULL */
- if (bitmap1 && (*bitmap1 & bitmask) == 0)
- {
- isnull1 = true;
- elt1 = (Datum) 0;
- }
- else
- {
- isnull1 = false;
- elt1 = fetch_att(ptr1, typbyval, typlen);
- ptr1 = att_addlength_pointer(ptr1, typlen, ptr1);
- ptr1 = (char *) att_align_nominal(ptr1, typalign);
- }
-
- /* advance bitmap pointer if any */
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- if (bitmap1)
- bitmap1++;
- bitmask = 1;
- }
+ elt1 = array_iter_next(&it1, &isnull1, i, typlen, typbyval, typalign);
/*
* We assume that the comparison operator is strict, so a NULL can't
}
}
- pfree(values2);
- pfree(nulls2);
-
return result;
}
Datum
arrayoverlap(PG_FUNCTION_ARGS)
{
- ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
- ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array1, 0);
- PG_FREE_IF_COPY(array2, 1);
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
Datum
arraycontains(PG_FUNCTION_ARGS)
{
- ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
- ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array1, 0);
- PG_FREE_IF_COPY(array2, 1);
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
Datum
arraycontained(PG_FUNCTION_ARGS)
{
- ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
- ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
+ AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
- PG_FREE_IF_COPY(array1, 0);
- PG_FREE_IF_COPY(array2, 1);
+ AARR_FREE_IF_COPY(array1, 0);
+ AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
astate->mcontext = arr_context;
astate->private_cxt = subcontext;
- astate->alen = (subcontext ? 64 : 8); /* arbitrary starting array size */
+ astate->alen = (subcontext ? 64 : 8); /* arbitrary starting array
+ * size */
astate->dvalues = (Datum *)
MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
astate->dnulls = (bool *)
bool subcontext)
{
ArrayBuildStateArr *astate;
- MemoryContext arr_context = rcontext; /* by default use the parent ctx */
+ MemoryContext arr_context = rcontext; /* by default use the parent
+ * ctx */
/* Lookup element type, unless element_type already provided */
- if (! OidIsValid(element_type))
+ if (!OidIsValid(element_type))
{
element_type = get_element_type(array_type);
Datum
array_larger(PG_FUNCTION_ARGS)
{
- ArrayType *v1,
- *v2,
- *result;
-
- v1 = PG_GETARG_ARRAYTYPE_P(0);
- v2 = PG_GETARG_ARRAYTYPE_P(1);
-
- result = ((array_cmp(fcinfo) > 0) ? v1 : v2);
-
- PG_RETURN_ARRAYTYPE_P(result);
+ if (array_cmp(fcinfo) > 0)
+ PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+ else
+ PG_RETURN_DATUM(PG_GETARG_DATUM(1));
}
Datum
array_smaller(PG_FUNCTION_ARGS)
{
- ArrayType *v1,
- *v2,
- *result;
-
- v1 = PG_GETARG_ARRAYTYPE_P(0);
- v2 = PG_GETARG_ARRAYTYPE_P(1);
-
- result = ((array_cmp(fcinfo) < 0) ? v1 : v2);
-
- PG_RETURN_ARRAYTYPE_P(result);
+ if (array_cmp(fcinfo) < 0)
+ PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+ else
+ PG_RETURN_DATUM(PG_GETARG_DATUM(1));
}
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
- ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
+ AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
int reqdim = PG_GETARG_INT32(1);
int *lb,
*dimv;
funcctx = SRF_FIRSTCALL_INIT();
/* Sanity check: does it look like an array at all? */
- if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
+ if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
SRF_RETURN_DONE(funcctx);
/* Sanity check: was the requested dim valid */
- if (reqdim <= 0 || reqdim > ARR_NDIM(v))
+ if (reqdim <= 0 || reqdim > AARR_NDIM(v))
SRF_RETURN_DONE(funcctx);
/*
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = (generate_subscripts_fctx *) palloc(sizeof(generate_subscripts_fctx));
- lb = ARR_LBOUND(v);
- dimv = ARR_DIMS(v);
+ lb = AARR_LBOUND(v);
+ dimv = AARR_DIMS(v);
fctx->lower = lb[reqdim - 1];
fctx->upper = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
{
typedef struct
{
- ArrayType *arr;
+ array_iter iter;
int nextelem;
int numelems;
- char *elemdataptr; /* this moves with nextelem */
- bits8 *arraynullsptr; /* this does not */
int16 elmlen;
bool elmbyval;
char elmalign;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
- ArrayType *arr;
+ AnyArrayType *arr;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
* and not before. (If no detoast happens, we assume the originally
* passed array will stick around till then.)
*/
- arr = PG_GETARG_ARRAYTYPE_P(0);
+ arr = PG_GETARG_ANY_ARRAY(0);
/* allocate memory for user context */
fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx));
/* initialize state */
- fctx->arr = arr;
+ array_iter_setup(&fctx->iter, arr);
fctx->nextelem = 0;
- fctx->numelems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
-
- fctx->elemdataptr = ARR_DATA_PTR(arr);
- fctx->arraynullsptr = ARR_NULLBITMAP(arr);
+ fctx->numelems = ArrayGetNItems(AARR_NDIM(arr), AARR_DIMS(arr));
- get_typlenbyvalalign(ARR_ELEMTYPE(arr),
- &fctx->elmlen,
- &fctx->elmbyval,
- &fctx->elmalign);
+ if (VARATT_IS_EXPANDED_HEADER(arr))
+ {
+ /* we can just grab the type data from expanded array */
+ fctx->elmlen = arr->xpn.typlen;
+ fctx->elmbyval = arr->xpn.typbyval;
+ fctx->elmalign = arr->xpn.typalign;
+ }
+ else
+ get_typlenbyvalalign(AARR_ELEMTYPE(arr),
+ &fctx->elmlen,
+ &fctx->elmbyval,
+ &fctx->elmalign);
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
int offset = fctx->nextelem++;
Datum elem;
- /*
- * Check for NULL array element
- */
- if (array_get_isnull(fctx->arraynullsptr, offset))
- {
- fcinfo->isnull = true;
- elem = (Datum) 0;
- /* elemdataptr does not move */
- }
- else
- {
- /*
- * OK, get the element
- */
- char *ptr = fctx->elemdataptr;
-
- fcinfo->isnull = false;
- elem = ArrayCast(ptr, fctx->elmbyval, fctx->elmlen);
-
- /*
- * Advance elemdataptr over it
- */
- ptr = att_addlength_pointer(ptr, fctx->elmlen, ptr);
- ptr = (char *) att_align_nominal(ptr, fctx->elmalign);
- fctx->elemdataptr = ptr;
- }
+ elem = array_iter_next(&fctx->iter, &fcinfo->isnull, offset,
+ fctx->elmlen, fctx->elmbyval, fctx->elmalign);
SRF_RETURN_NEXT(funcctx, elem);
}
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = element_type;
- memcpy(ARR_DIMS(result), ARR_DIMS(array), 2 * ndim * sizeof(int));
+ memcpy(ARR_DIMS(result), ARR_DIMS(array), ndim * sizeof(int));
+ memcpy(ARR_LBOUND(result), ARR_LBOUND(array), ndim * sizeof(int));
if (remove)
{
*
*-------------------------------------------------------------------------
*/
+
/*
- * In the implementation of the next routines we assume the following:
+ * In the implementation of these routines we assume the following:
*
* A) if a type is "byVal" then all the information is stored in the
* Datum itself (i.e. no pointers involved!). In this case the
*
* Note that we do not treat "toasted" datums specially; therefore what
* will be copied or compared is the compressed data or toast reference.
+ * An exception is made for datumCopy() of an expanded object, however,
+ * because most callers expect to get a simple contiguous (and pfree'able)
+ * result from datumCopy(). See also datumTransfer().
*/
#include "postgres.h"
#include "utils/datum.h"
+#include "utils/expandeddatum.h"
/*-------------------------------------------------------------------------
*
* Find the "real" size of a datum, given the datum value,
* whether it is a "by value", and the declared type length.
+ * (For TOAST pointer datums, this is the size of the pointer datum.)
*
* This is essentially an out-of-line version of the att_addlength_datum()
* macro in access/tupmacs.h. We do a tad more error checking though.
/*-------------------------------------------------------------------------
* datumCopy
*
- * make a copy of a datum
+ * Make a copy of a non-NULL datum.
*
* If the datatype is pass-by-reference, memory is obtained with palloc().
+ *
+ * If the value is a reference to an expanded object, we flatten into memory
+ * obtained with palloc(). We need to copy because one of the main uses of
+ * this function is to copy a datum out of a transient memory context that's
+ * about to be destroyed, and the expanded object is probably in a child
+ * context that will also go away. Moreover, many callers assume that the
+ * result is a single pfree-able chunk.
*-------------------------------------------------------------------------
*/
Datum
if (typByVal)
res = value;
+ else if (typLen == -1)
+ {
+ /* It is a varlena datatype */
+ struct varlena *vl = (struct varlena *) DatumGetPointer(value);
+
+ if (VARATT_IS_EXTERNAL_EXPANDED(vl))
+ {
+ /* Flatten into the caller's memory context */
+ ExpandedObjectHeader *eoh = DatumGetEOHP(value);
+ Size resultsize;
+ char *resultptr;
+
+ resultsize = EOH_get_flat_size(eoh);
+ resultptr = (char *) palloc(resultsize);
+ EOH_flatten_into(eoh, (void *) resultptr, resultsize);
+ res = PointerGetDatum(resultptr);
+ }
+ else
+ {
+ /* Otherwise, just copy the varlena datum verbatim */
+ Size realSize;
+ char *resultptr;
+
+ realSize = (Size) VARSIZE_ANY(vl);
+ resultptr = (char *) palloc(realSize);
+ memcpy(resultptr, vl, realSize);
+ res = PointerGetDatum(resultptr);
+ }
+ }
else
{
+ /* Pass by reference, but not varlena, so not toasted */
Size realSize;
- char *s;
-
- if (DatumGetPointer(value) == NULL)
- return PointerGetDatum(NULL);
+ char *resultptr;
realSize = datumGetSize(value, typByVal, typLen);
- s = (char *) palloc(realSize);
- memcpy(s, DatumGetPointer(value), realSize);
- res = PointerGetDatum(s);
+ resultptr = (char *) palloc(realSize);
+ memcpy(resultptr, DatumGetPointer(value), realSize);
+ res = PointerGetDatum(resultptr);
}
return res;
}
/*-------------------------------------------------------------------------
- * datumFree
+ * datumTransfer
*
- * Free the space occupied by a datum CREATED BY "datumCopy"
+ * Transfer a non-NULL datum into the current memory context.
*
- * NOTE: DO NOT USE THIS ROUTINE with datums returned by heap_getattr() etc.
- * ONLY datums created by "datumCopy" can be freed!
+ * This is equivalent to datumCopy() except when the datum is a read-write
+ * pointer to an expanded object. In that case we merely reparent the object
+ * into the current context, and return its standard R/W pointer (in case the
+ * given one is a transient pointer of shorter lifespan).
*-------------------------------------------------------------------------
*/
-#ifdef NOT_USED
-void
-datumFree(Datum value, bool typByVal, int typLen)
+Datum
+datumTransfer(Datum value, bool typByVal, int typLen)
{
- if (!typByVal)
- {
- Pointer s = DatumGetPointer(value);
-
- pfree(s);
- }
+ if (!typByVal && typLen == -1 &&
+ VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(value)))
+ value = TransferExpandedObject(value, CurrentMemoryContext);
+ else
+ value = datumCopy(value, typByVal, typLen);
+ return value;
}
-#endif
/*-------------------------------------------------------------------------
* datumIsEqual
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * expandeddatum.c
+ * Support functions for "expanded" value representations.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/expandeddatum.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "utils/expandeddatum.h"
+#include "utils/memutils.h"
+
+/*
+ * DatumGetEOHP
+ *
+ * Given a Datum that is an expanded-object reference, extract the pointer.
+ *
+ * This is a bit tedious since the pointer may not be properly aligned;
+ * compare VARATT_EXTERNAL_GET_POINTER().
+ */
+ExpandedObjectHeader *
+DatumGetEOHP(Datum d)
+{
+ varattrib_1b_e *datum = (varattrib_1b_e *) DatumGetPointer(d);
+ varatt_expanded ptr;
+
+ Assert(VARATT_IS_EXTERNAL_EXPANDED(datum));
+ memcpy(&ptr, VARDATA_EXTERNAL(datum), sizeof(ptr));
+ Assert(VARATT_IS_EXPANDED_HEADER(ptr.eohptr));
+ return ptr.eohptr;
+}
+
+/*
+ * EOH_init_header
+ *
+ * Initialize the common header of an expanded object.
+ *
+ * The main thing this encapsulates is initializing the TOAST pointers.
+ */
+void
+EOH_init_header(ExpandedObjectHeader *eohptr,
+ const ExpandedObjectMethods *methods,
+ MemoryContext obj_context)
+{
+ varatt_expanded ptr;
+
+ eohptr->vl_len_ = EOH_HEADER_MAGIC;
+ eohptr->eoh_methods = methods;
+ eohptr->eoh_context = obj_context;
+
+ ptr.eohptr = eohptr;
+
+ SET_VARTAG_EXTERNAL(eohptr->eoh_rw_ptr, VARTAG_EXPANDED_RW);
+ memcpy(VARDATA_EXTERNAL(eohptr->eoh_rw_ptr), &ptr, sizeof(ptr));
+
+ SET_VARTAG_EXTERNAL(eohptr->eoh_ro_ptr, VARTAG_EXPANDED_RO);
+ memcpy(VARDATA_EXTERNAL(eohptr->eoh_ro_ptr), &ptr, sizeof(ptr));
+}
+
+/*
+ * EOH_get_flat_size
+ * EOH_flatten_into
+ *
+ * Convenience functions for invoking the "methods" of an expanded object.
+ */
+
+Size
+EOH_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+ return (*eohptr->eoh_methods->get_flat_size) (eohptr);
+}
+
+void
+EOH_flatten_into(ExpandedObjectHeader *eohptr,
+ void *result, Size allocated_size)
+{
+ (*eohptr->eoh_methods->flatten_into) (eohptr, result, allocated_size);
+}
+
+/*
+ * Does the Datum represent a writable expanded object?
+ */
+bool
+DatumIsReadWriteExpandedObject(Datum d, bool isnull, int16 typlen)
+{
+ /* Reject if it's NULL or not a varlena type */
+ if (isnull || typlen != -1)
+ return false;
+
+ /* Reject if not a read-write expanded-object pointer */
+ if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
+ return false;
+
+ return true;
+}
+
+/*
+ * If the Datum represents a R/W expanded object, change it to R/O.
+ * Otherwise return the original Datum.
+ */
+Datum
+MakeExpandedObjectReadOnly(Datum d, bool isnull, int16 typlen)
+{
+ ExpandedObjectHeader *eohptr;
+
+ /* Nothing to do if it's NULL or not a varlena type */
+ if (isnull || typlen != -1)
+ return d;
+
+ /* Nothing to do if not a read-write expanded-object pointer */
+ if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
+ return d;
+
+ /* Now safe to extract the object pointer */
+ eohptr = DatumGetEOHP(d);
+
+ /* Return the built-in read-only pointer instead of given pointer */
+ return EOHPGetRODatum(eohptr);
+}
+
+/*
+ * Transfer ownership of an expanded object to a new parent memory context.
+ * The object must be referenced by a R/W pointer, and what we return is
+ * always its "standard" R/W pointer, which is certain to have the same
+ * lifespan as the object itself. (The passed-in pointer might not, and
+ * in any case wouldn't provide a unique identifier if it's not that one.)
+ */
+Datum
+TransferExpandedObject(Datum d, MemoryContext new_parent)
+{
+ ExpandedObjectHeader *eohptr = DatumGetEOHP(d);
+
+ /* Assert caller gave a R/W pointer */
+ Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)));
+
+ /* Transfer ownership */
+ MemoryContextSetParent(eohptr->eoh_context, new_parent);
+
+ /* Return the object's standard read-write pointer */
+ return EOHPGetRWDatum(eohptr);
+}
+
+/*
+ * Delete an expanded object (must be referenced by a R/W pointer).
+ */
+void
+DeleteExpandedObject(Datum d)
+{
+ ExpandedObjectHeader *eohptr = DatumGetEOHP(d);
+
+ /* Assert caller gave a R/W pointer */
+ Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)));
+
+ /* Kill it */
+ MemoryContextDelete(eohptr->eoh_context);
+}
AssertArg(MemoryContextIsValid(context));
AssertArg(context != new_parent);
+ /* Fast path if it's got correct parent already */
+ if (new_parent == context->parent)
+ return;
+
/* Delink from existing parent, if any */
if (context->parent)
{
extern void *SPI_palloc(Size size);
extern void *SPI_repalloc(void *pointer, Size size);
extern void SPI_pfree(void *pointer);
+extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen);
extern void SPI_freetuple(HeapTuple pointer);
extern void SPI_freetuptable(SPITupleTable *tuptable);
extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
TupleTableSlot *srcslot);
+extern TupleTableSlot *ExecMakeSlotContentsReadOnly(TupleTableSlot *slot);
/* in access/common/heaptuple.c */
extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
* Note: the result datatype is the element type when fetching a single
* element; but it is the array type when doing subarray fetch or either
* type of store.
+ *
+ * Note: for the cases where an array is returned, if refexpr yields a R/W
+ * expanded array, then the implementation is allowed to modify that object
+ * in-place and return the same object.)
* ----------------
*/
typedef struct ArrayRef
struct varlena *pointer; /* Pointer to in-memory varlena */
} varatt_indirect;
+/*
+ * struct varatt_expanded is a "TOAST pointer" representing an out-of-line
+ * Datum that is stored in memory, in some type-specific, not necessarily
+ * physically contiguous format that is convenient for computation not
+ * storage. APIs for this, in particular the definition of struct
+ * ExpandedObjectHeader, are in src/include/utils/expandeddatum.h.
+ *
+ * Note that just as for struct varatt_external, this struct is stored
+ * unaligned within any containing tuple.
+ */
+typedef struct ExpandedObjectHeader ExpandedObjectHeader;
+
+typedef struct varatt_expanded
+{
+ ExpandedObjectHeader *eohptr;
+} varatt_expanded;
+
/*
* Type tag for the various sorts of "TOAST pointer" datums. The peculiar
* value for VARTAG_ONDISK comes from a requirement for on-disk compatibility
typedef enum vartag_external
{
VARTAG_INDIRECT = 1,
+ VARTAG_EXPANDED_RO = 2,
+ VARTAG_EXPANDED_RW = 3,
VARTAG_ONDISK = 18
} vartag_external;
+/* this test relies on the specific tag values above */
+#define VARTAG_IS_EXPANDED(tag) \
+ (((tag) & ~1) == VARTAG_EXPANDED_RO)
+
#define VARTAG_SIZE(tag) \
((tag) == VARTAG_INDIRECT ? sizeof(varatt_indirect) : \
+ VARTAG_IS_EXPANDED(tag) ? sizeof(varatt_expanded) : \
(tag) == VARTAG_ONDISK ? sizeof(varatt_external) : \
TrapMacro(true, "unrecognized TOAST vartag"))
(VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_ONDISK)
#define VARATT_IS_EXTERNAL_INDIRECT(PTR) \
(VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_INDIRECT)
+#define VARATT_IS_EXTERNAL_EXPANDED_RO(PTR) \
+ (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_EXPANDED_RO)
+#define VARATT_IS_EXTERNAL_EXPANDED_RW(PTR) \
+ (VARATT_IS_EXTERNAL(PTR) && VARTAG_EXTERNAL(PTR) == VARTAG_EXPANDED_RW)
+#define VARATT_IS_EXTERNAL_EXPANDED(PTR) \
+ (VARATT_IS_EXTERNAL(PTR) && VARTAG_IS_EXPANDED(VARTAG_EXTERNAL(PTR)))
#define VARATT_IS_SHORT(PTR) VARATT_IS_1B(PTR)
#define VARATT_IS_EXTENDED(PTR) (!VARATT_IS_4B_U(PTR))
* We support subscripting on these types, but array_in() and array_out()
* only work with varlena arrays.
*
+ * In addition, arrays are a major user of the "expanded object" TOAST
+ * infrastructure. This allows a varlena array to be converted to a
+ * separate representation that may include "deconstructed" Datum/isnull
+ * arrays holding the elements.
+ *
*
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
#define ARRAY_H
#include "fmgr.h"
+#include "utils/expandeddatum.h"
+
/*
* Arrays are varlena objects, so must meet the varlena convention that
Oid elemtype; /* element type OID */
} ArrayType;
+/*
+ * An expanded array is contained within a private memory context (as
+ * all expanded objects must be) and has a control structure as below.
+ *
+ * The expanded array might contain a regular "flat" array if that was the
+ * original input and we've not modified it significantly. Otherwise, the
+ * contents are represented by Datum/isnull arrays plus dimensionality and
+ * type information. We could also have both forms, if we've deconstructed
+ * the original array for access purposes but not yet changed it. For pass-
+ * by-reference element types, the Datums would point into the flat array in
+ * this situation. Once we start modifying array elements, new pass-by-ref
+ * elements are separately palloc'd within the memory context.
+ */
+#define EA_MAGIC 689375833 /* ID for debugging crosschecks */
+
+typedef struct ExpandedArrayHeader
+{
+ /* Standard header for expanded objects */
+ ExpandedObjectHeader hdr;
+
+ /* Magic value identifying an expanded array (for debugging only) */
+ int ea_magic;
+
+ /* Dimensionality info (always valid) */
+ int ndims; /* # of dimensions */
+ int *dims; /* array dimensions */
+ int *lbound; /* index lower bounds for each dimension */
+
+ /* Element type info (always valid) */
+ Oid element_type; /* element type OID */
+ int16 typlen; /* needed info about element datatype */
+ bool typbyval;
+ char typalign;
+
+ /*
+ * If we have a Datum-array representation of the array, it's kept here;
+ * else dvalues/dnulls are NULL. The dvalues and dnulls arrays are always
+ * palloc'd within the object private context, but may change size from
+ * time to time. For pass-by-ref element types, dvalues entries might
+ * point either into the fstartptr..fendptr area, or to separately
+ * palloc'd chunks. Elements should always be fully detoasted, as they
+ * are in the standard flat representation.
+ *
+ * Even when dvalues is valid, dnulls can be NULL if there are no null
+ * elements.
+ */
+ Datum *dvalues; /* array of Datums */
+ bool *dnulls; /* array of is-null flags for Datums */
+ int dvalueslen; /* allocated length of above arrays */
+ int nelems; /* number of valid entries in above arrays */
+
+ /*
+ * flat_size is the current space requirement for the flat equivalent of
+ * the expanded array, if known; otherwise it's 0. We store this to make
+ * consecutive calls of get_flat_size cheap.
+ */
+ Size flat_size;
+
+ /*
+ * fvalue points to the flat representation if it is valid, else it is
+ * NULL. If we have or ever had a flat representation then
+ * fstartptr/fendptr point to the start and end+1 of its data area; this
+ * is so that we can tell which Datum pointers point into the flat
+ * representation rather than being pointers to separately palloc'd data.
+ */
+ ArrayType *fvalue; /* must be a fully detoasted array */
+ char *fstartptr; /* start of its data area */
+ char *fendptr; /* end+1 of its data area */
+} ExpandedArrayHeader;
+
+/*
+ * Functions that can handle either a "flat" varlena array or an expanded
+ * array use this union to work with their input.
+ */
+typedef union AnyArrayType
+{
+ ArrayType flt;
+ ExpandedArrayHeader xpn;
+} AnyArrayType;
+
/*
* working state for accumArrayResult() and friends
* note that the input must be scalars (legal array elements)
/* ArrayIteratorData is private in arrayfuncs.c */
typedef struct ArrayIteratorData *ArrayIterator;
-/*
- * fmgr macros for array objects
- */
+/* fmgr macros for regular varlena array objects */
#define DatumGetArrayTypeP(X) ((ArrayType *) PG_DETOAST_DATUM(X))
#define DatumGetArrayTypePCopy(X) ((ArrayType *) PG_DETOAST_DATUM_COPY(X))
#define PG_GETARG_ARRAYTYPE_P(n) DatumGetArrayTypeP(PG_GETARG_DATUM(n))
#define PG_GETARG_ARRAYTYPE_P_COPY(n) DatumGetArrayTypePCopy(PG_GETARG_DATUM(n))
#define PG_RETURN_ARRAYTYPE_P(x) PG_RETURN_POINTER(x)
+/* fmgr macros for expanded array objects */
+#define PG_GETARG_EXPANDED_ARRAY(n) DatumGetExpandedArray(PG_GETARG_DATUM(n))
+#define PG_GETARG_EXPANDED_ARRAYX(n, metacache) \
+ DatumGetExpandedArrayX(PG_GETARG_DATUM(n), metacache)
+#define PG_RETURN_EXPANDED_ARRAY(x) PG_RETURN_DATUM(EOHPGetRWDatum(&(x)->hdr))
+
+/* fmgr macros for AnyArrayType (ie, get either varlena or expanded form) */
+#define PG_GETARG_ANY_ARRAY(n) DatumGetAnyArray(PG_GETARG_DATUM(n))
+
/*
- * Access macros for array header fields.
+ * Access macros for varlena array header fields.
*
* ARR_DIMS returns a pointer to an array of array dimensions (number of
* elements along the various array axes).
#define ARR_DATA_PTR(a) \
(((char *) (a)) + ARR_DATA_OFFSET(a))
+/*
+ * Macros for working with AnyArrayType inputs. Beware multiple references!
+ */
+#define AARR_NDIM(a) \
+ (VARATT_IS_EXPANDED_HEADER(a) ? (a)->xpn.ndims : ARR_NDIM(&(a)->flt))
+#define AARR_HASNULL(a) \
+ (VARATT_IS_EXPANDED_HEADER(a) ? \
+ ((a)->xpn.dvalues != NULL ? (a)->xpn.dnulls != NULL : ARR_HASNULL((a)->xpn.fvalue)) : \
+ ARR_HASNULL(&(a)->flt))
+#define AARR_ELEMTYPE(a) \
+ (VARATT_IS_EXPANDED_HEADER(a) ? (a)->xpn.element_type : ARR_ELEMTYPE(&(a)->flt))
+#define AARR_DIMS(a) \
+ (VARATT_IS_EXPANDED_HEADER(a) ? (a)->xpn.dims : ARR_DIMS(&(a)->flt))
+#define AARR_LBOUND(a) \
+ (VARATT_IS_EXPANDED_HEADER(a) ? (a)->xpn.lbound : ARR_LBOUND(&(a)->flt))
+
/*
* GUC parameter
extern Datum array_replace(PG_FUNCTION_ARGS);
extern Datum width_bucket_array(PG_FUNCTION_ARGS);
+extern void CopyArrayEls(ArrayType *array,
+ Datum *values,
+ bool *nulls,
+ int nitems,
+ int typlen,
+ bool typbyval,
+ char typalign,
+ bool freedata);
+
extern Datum array_get_element(Datum arraydatum, int nSubscripts, int *indx,
int arraytyplen, int elmlen, bool elmbyval, char elmalign,
bool *isNull);
Datum dataValue, bool isNull,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);
-extern Datum array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType,
+extern Datum array_map(FunctionCallInfo fcinfo, Oid retType,
ArrayMapState *amstate);
extern void array_bitmap_copy(bits8 *destbitmap, int destoffset,
int *lbs,
Oid elmtype, int elmlen, bool elmbyval, char elmalign);
extern ArrayType *construct_empty_array(Oid elmtype);
+extern ExpandedArrayHeader *construct_empty_expanded_array(Oid element_type,
+ MemoryContext parentcontext,
+ ArrayMetaState *metacache);
extern void deconstruct_array(ArrayType *array,
Oid elmtype,
int elmlen, bool elmbyval, char elmalign,
extern int mda_next_tuple(int n, int *curr, const int *span);
extern int32 *ArrayGetIntegerTypmods(ArrayType *arr, int *n);
+/*
+ * prototypes for functions defined in array_expanded.c
+ */
+extern Datum expand_array(Datum arraydatum, MemoryContext parentcontext,
+ ArrayMetaState *metacache);
+extern ExpandedArrayHeader *DatumGetExpandedArray(Datum d);
+extern ExpandedArrayHeader *DatumGetExpandedArrayX(Datum d,
+ ArrayMetaState *metacache);
+extern AnyArrayType *DatumGetAnyArray(Datum d);
+extern void deconstruct_expanded_array(ExpandedArrayHeader *eah);
+
/*
* prototypes for functions defined in array_userfuncs.c
*/
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * arrayaccess.h
+ * Declarations for element-by-element access to Postgres arrays.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/arrayaccess.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ARRAYACCESS_H
+#define ARRAYACCESS_H
+
+#include "access/tupmacs.h"
+#include "utils/array.h"
+
+
+/*
+ * Functions for iterating through elements of a flat or expanded array.
+ * These require a state struct "array_iter iter".
+ *
+ * Use "array_iter_setup(&iter, arrayptr);" to prepare to iterate, and
+ * "datumvar = array_iter_next(&iter, &isnullvar, index, ...);" to fetch
+ * the next element into datumvar/isnullvar.
+ * "index" must be the zero-origin element number; we make caller provide
+ * this since caller is generally counting the elements anyway. Despite
+ * that, these functions can only fetch elements sequentially.
+ */
+
+typedef struct array_iter
+{
+ /* datumptr being NULL or not tells if we have flat or expanded array */
+
+ /* Fields used when we have an expanded array */
+ Datum *datumptr; /* Pointer to Datum array */
+ bool *isnullptr; /* Pointer to isnull array */
+
+ /* Fields used when we have a flat array */
+ char *dataptr; /* Current spot in the data area */
+ bits8 *bitmapptr; /* Current byte of the nulls bitmap, or NULL */
+ int bitmask; /* mask for current bit in nulls bitmap */
+} array_iter;
+
+/*
+ * We want the functions below to be inline; but if the compiler doesn't
+ * support that, fall back on providing them as regular functions. See
+ * STATIC_IF_INLINE in c.h.
+ */
+#ifndef PG_USE_INLINE
+extern void array_iter_setup(array_iter *it, AnyArrayType *a);
+extern Datum array_iter_next(array_iter *it, bool *isnull, int i,
+ int elmlen, bool elmbyval, char elmalign);
+#endif /* !PG_USE_INLINE */
+
+#if defined(PG_USE_INLINE) || defined(ARRAYACCESS_INCLUDE_DEFINITIONS)
+
+STATIC_IF_INLINE void
+array_iter_setup(array_iter *it, AnyArrayType *a)
+{
+ if (VARATT_IS_EXPANDED_HEADER(a))
+ {
+ if (a->xpn.dvalues)
+ {
+ it->datumptr = a->xpn.dvalues;
+ it->isnullptr = a->xpn.dnulls;
+ /* we must fill all fields to prevent compiler warnings */
+ it->dataptr = NULL;
+ it->bitmapptr = NULL;
+ }
+ else
+ {
+ /* Work with flat array embedded in the expanded datum */
+ it->datumptr = NULL;
+ it->isnullptr = NULL;
+ it->dataptr = ARR_DATA_PTR(a->xpn.fvalue);
+ it->bitmapptr = ARR_NULLBITMAP(a->xpn.fvalue);
+ }
+ }
+ else
+ {
+ it->datumptr = NULL;
+ it->isnullptr = NULL;
+ it->dataptr = ARR_DATA_PTR(&a->flt);
+ it->bitmapptr = ARR_NULLBITMAP(&a->flt);
+ }
+ it->bitmask = 1;
+}
+
+STATIC_IF_INLINE Datum
+array_iter_next(array_iter *it, bool *isnull, int i,
+ int elmlen, bool elmbyval, char elmalign)
+{
+ Datum ret;
+
+ if (it->datumptr)
+ {
+ ret = it->datumptr[i];
+ *isnull = it->isnullptr ? it->isnullptr[i] : false;
+ }
+ else
+ {
+ if (it->bitmapptr && (*(it->bitmapptr) & it->bitmask) == 0)
+ {
+ *isnull = true;
+ ret = (Datum) 0;
+ }
+ else
+ {
+ *isnull = false;
+ ret = fetch_att(it->dataptr, elmbyval, elmlen);
+ it->dataptr = att_addlength_pointer(it->dataptr, elmlen,
+ it->dataptr);
+ it->dataptr = (char *) att_align_nominal(it->dataptr, elmalign);
+ }
+ it->bitmask <<= 1;
+ if (it->bitmask == 0x100)
+ {
+ if (it->bitmapptr)
+ it->bitmapptr++;
+ it->bitmask = 1;
+ }
+ }
+
+ return ret;
+}
+
+#endif /* defined(PG_USE_INLINE) ||
+ * defined(ARRAYACCESS_INCLUDE_DEFINITIONS) */
+
+#endif /* ARRAYACCESS_H */
extern Size datumGetSize(Datum value, bool typByVal, int typLen);
/*
- * datumCopy - make a copy of a datum.
+ * datumCopy - make a copy of a non-NULL datum.
*
* If the datatype is pass-by-reference, memory is obtained with palloc().
*/
extern Datum datumCopy(Datum value, bool typByVal, int typLen);
/*
- * datumFree - free a datum previously allocated by datumCopy, if any.
+ * datumTransfer - transfer a non-NULL datum into the current memory context.
*
- * Does nothing if datatype is pass-by-value.
+ * Differs from datumCopy() in its handling of read-write expanded objects.
*/
-extern void datumFree(Datum value, bool typByVal, int typLen);
+extern Datum datumTransfer(Datum value, bool typByVal, int typLen);
/*
* datumIsEqual
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * expandeddatum.h
+ * Declarations for access to "expanded" value representations.
+ *
+ * Complex data types, particularly container types such as arrays and
+ * records, usually have on-disk representations that are compact but not
+ * especially convenient to modify. What's more, when we do modify them,
+ * having to recopy all the rest of the value can be extremely inefficient.
+ * Therefore, we provide a notion of an "expanded" representation that is used
+ * only in memory and is optimized more for computation than storage.
+ * The format appearing on disk is called the data type's "flattened"
+ * representation, since it is required to be a contiguous blob of bytes --
+ * but the type can have an expanded representation that is not. Data types
+ * must provide means to translate an expanded representation back to
+ * flattened form.
+ *
+ * An expanded object is meant to survive across multiple operations, but
+ * not to be enormously long-lived; for example it might be a local variable
+ * in a PL/pgSQL procedure. So its extra bulk compared to the on-disk format
+ * is a worthwhile trade-off.
+ *
+ * References to expanded objects are a type of TOAST pointer.
+ * Because of longstanding conventions in Postgres, this means that the
+ * flattened form of such an object must always be a varlena object.
+ * Fortunately that's no restriction in practice.
+ *
+ * There are actually two kinds of TOAST pointers for expanded objects:
+ * read-only and read-write pointers. Possession of one of the latter
+ * authorizes a function to modify the value in-place rather than copying it
+ * as would normally be required. Functions should always return a read-write
+ * pointer to any new expanded object they create. Functions that modify an
+ * argument value in-place must take care that they do not corrupt the old
+ * value if they fail partway through.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/expandeddatum.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXPANDEDDATUM_H
+#define EXPANDEDDATUM_H
+
+/* Size of an EXTERNAL datum that contains a pointer to an expanded object */
+#define EXPANDED_POINTER_SIZE (VARHDRSZ_EXTERNAL + sizeof(varatt_expanded))
+
+/*
+ * "Methods" that must be provided for any expanded object.
+ *
+ * get_flat_size: compute space needed for flattened representation (total,
+ * including header).
+ *
+ * flatten_into: construct flattened representation in the caller-allocated
+ * space at *result, of size allocated_size (which will always be the result
+ * of a preceding get_flat_size call; it's passed for cross-checking).
+ *
+ * The flattened representation must be a valid in-line, non-compressed,
+ * 4-byte-header varlena object.
+ *
+ * Note: construction of a heap tuple from an expanded datum calls
+ * get_flat_size twice, so it's worthwhile to make sure that that doesn't
+ * incur too much overhead.
+ */
+typedef Size (*EOM_get_flat_size_method) (ExpandedObjectHeader *eohptr);
+typedef void (*EOM_flatten_into_method) (ExpandedObjectHeader *eohptr,
+ void *result, Size allocated_size);
+
+/* Struct of function pointers for an expanded object's methods */
+typedef struct ExpandedObjectMethods
+{
+ EOM_get_flat_size_method get_flat_size;
+ EOM_flatten_into_method flatten_into;
+} ExpandedObjectMethods;
+
+/*
+ * Every expanded object must contain this header; typically the header
+ * is embedded in some larger struct that adds type-specific fields.
+ *
+ * It is presumed that the header object and all subsidiary data are stored
+ * in eoh_context, so that the object can be freed by deleting that context,
+ * or its storage lifespan can be altered by reparenting the context.
+ * (In principle the object could own additional resources, such as malloc'd
+ * storage, and use a memory context reset callback to free them upon reset or
+ * deletion of eoh_context.)
+ *
+ * We set up two TOAST pointers within the standard header, one read-write
+ * and one read-only. This allows functions to return either kind of pointer
+ * without making an additional allocation, and in particular without worrying
+ * whether a separately palloc'd object would have sufficient lifespan.
+ * But note that these pointers are just a convenience; a pointer object
+ * appearing somewhere else would still be legal.
+ *
+ * The typedef declaration for this appears in postgres.h.
+ */
+struct ExpandedObjectHeader
+{
+ /* Phony varlena header */
+ int32 vl_len_; /* always EOH_HEADER_MAGIC, see below */
+
+ /* Pointer to methods required for object type */
+ const ExpandedObjectMethods *eoh_methods;
+
+ /* Memory context containing this header and subsidiary data */
+ MemoryContext eoh_context;
+
+ /* Standard R/W TOAST pointer for this object is kept here */
+ char eoh_rw_ptr[EXPANDED_POINTER_SIZE];
+
+ /* Standard R/O TOAST pointer for this object is kept here */
+ char eoh_ro_ptr[EXPANDED_POINTER_SIZE];
+};
+
+/*
+ * Particularly for read-only functions, it is handy to be able to work with
+ * either regular "flat" varlena inputs or expanded inputs of the same data
+ * type. To allow determining which case an argument-fetching function has
+ * returned, the first int32 of an ExpandedObjectHeader always contains -1
+ * (EOH_HEADER_MAGIC to the code). This works since no 4-byte-header varlena
+ * could have that as its first 4 bytes. Caution: we could not reliably tell
+ * the difference between an ExpandedObjectHeader and a short-header object
+ * with this trick. However, it works fine if the argument fetching code
+ * always returns either a 4-byte-header flat object or an expanded object.
+ */
+#define EOH_HEADER_MAGIC (-1)
+#define VARATT_IS_EXPANDED_HEADER(PTR) \
+ (((ExpandedObjectHeader *) (PTR))->vl_len_ == EOH_HEADER_MAGIC)
+
+/*
+ * Generic support functions for expanded objects.
+ * (More of these might be worth inlining later.)
+ */
+
+#define EOHPGetRWDatum(eohptr) PointerGetDatum((eohptr)->eoh_rw_ptr)
+#define EOHPGetRODatum(eohptr) PointerGetDatum((eohptr)->eoh_ro_ptr)
+
+extern ExpandedObjectHeader *DatumGetEOHP(Datum d);
+extern void EOH_init_header(ExpandedObjectHeader *eohptr,
+ const ExpandedObjectMethods *methods,
+ MemoryContext obj_context);
+extern Size EOH_get_flat_size(ExpandedObjectHeader *eohptr);
+extern void EOH_flatten_into(ExpandedObjectHeader *eohptr,
+ void *result, Size allocated_size);
+extern bool DatumIsReadWriteExpandedObject(Datum d, bool isnull, int16 typlen);
+extern Datum MakeExpandedObjectReadOnly(Datum d, bool isnull, int16 typlen);
+extern Datum TransferExpandedObject(Datum d, MemoryContext new_parent);
+extern void DeleteExpandedObject(Datum d);
+
+#endif /* EXPANDEDDATUM_H */
typ->collation = typeStruct->typcollation;
if (OidIsValid(collation) && OidIsValid(typ->collation))
typ->collation = collation;
+ /* Detect if type is true array, or domain thereof */
+ /* NB: this is only used to decide whether to apply expand_array */
+ if (typeStruct->typtype == TYPTYPE_BASE)
+ {
+ /* this test should match what get_element_type() checks */
+ typ->typisarray = (typeStruct->typlen == -1 &&
+ OidIsValid(typeStruct->typelem));
+ }
+ else if (typeStruct->typtype == TYPTYPE_DOMAIN)
+ {
+ /* we can short-circuit looking up base types if it's not varlena */
+ typ->typisarray = (typeStruct->typlen == -1 &&
+ OidIsValid(get_base_element_type(typeStruct->typbasetype)));
+ }
+ else
+ typ->typisarray = false;
typ->atttypmod = typmod;
return typ;
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
+#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
static bool exec_simple_check_node(Node *node);
static void exec_simple_check_plan(PLpgSQL_expr *expr);
static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
+static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
+static bool contains_target_param(Node *node, int *target_dno);
static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
Datum *result,
var->value = fcinfo->arg[i];
var->isnull = fcinfo->argnull[i];
var->freeval = false;
+
+ /*
+ * Force any array-valued parameter to be stored in
+ * expanded form in our local variable, in hopes of
+ * improving efficiency of uses of the variable. (This is
+ * a hack, really: why only arrays? Need more thought
+ * about which cases are likely to win. See also
+ * typisarray-specific heuristic in exec_assign_value.)
+ *
+ * Special cases: If passed a R/W expanded pointer, assume
+ * we can commandeer the object rather than having to copy
+ * it. If passed a R/O expanded pointer, just keep it as
+ * the value of the variable for the moment. (We'll force
+ * it to R/W if the variable gets modified, but that may
+ * very well never happen.)
+ */
+ if (!var->isnull && var->datatype->typisarray)
+ {
+ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
+ {
+ /* take ownership of R/W object */
+ var->value = TransferExpandedObject(var->value,
+ CurrentMemoryContext);
+ var->freeval = true;
+ }
+ else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value)))
+ {
+ /* R/O pointer, keep it as-is until assigned to */
+ }
+ else
+ {
+ /* flat array, so force to expanded form */
+ var->value = expand_array(var->value,
+ CurrentMemoryContext,
+ NULL);
+ var->freeval = true;
+ }
+ }
}
break;
/*
* If the function's return type isn't by value, copy the value
- * into upper executor memory context.
+ * into upper executor memory context. However, if we have a R/W
+ * expanded datum, we can just transfer its ownership out to the
+ * upper executor context.
*/
if (!fcinfo->isnull && !func->fn_retbyval)
- {
- Size len;
- void *tmp;
-
- len = datumGetSize(estate.retval, false, func->fn_rettyplen);
- tmp = SPI_palloc(len);
- memcpy(tmp, DatumGetPointer(estate.retval), len);
- estate.retval = PointerGetDatum(tmp);
- }
+ estate.retval = SPI_datumTransfer(estate.retval,
+ false,
+ func->fn_rettyplen);
}
}
* Special case path when the RETURN expression is a simple variable
* reference; in particular, this path is always taken in functions with
* one or more OUT parameters.
+ *
+ * This special case is especially efficient for returning variables that
+ * have R/W expanded values: we can put the R/W pointer directly into
+ * estate->retval, leading to transferring the value to the caller's
+ * context cheaply. If we went through exec_eval_expr we'd end up with a
+ * R/O pointer. It's okay to skip MakeExpandedObjectReadOnly here since
+ * we know we won't need the variable's value within the function anymore.
*/
if (stmt->retvarno >= 0)
{
* Special case path when the RETURN NEXT expression is a simple variable
* reference; in particular, this path is always taken in functions with
* one or more OUT parameters.
+ *
+ * Unlike exec_statement_return, there's no special win here for R/W
+ * expanded values, since they'll have to get flattened to go into the
+ * tuplestore. Indeed, we'd better make them R/O to avoid any risk of the
+ * casting step changing them in-place.
*/
if (stmt->retvarno >= 0)
{
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong result type supplied in RETURN NEXT")));
+ /* let's be very paranoid about the cast step */
+ retval = MakeExpandedObjectReadOnly(retval,
+ isNull,
+ var->datatype->typlen);
+
/* coerce type if needed */
retval = exec_cast_value(estate,
retval,
/* Check to see if it's a simple expression */
exec_simple_check_plan(expr);
+
+ /*
+ * Mark expression as not using a read-write param. exec_assign_value has
+ * to take steps to override this if appropriate; that seems cleaner than
+ * adding parameters to all other callers.
+ */
+ expr->rwparam = -1;
}
Oid valtype;
int32 valtypmod;
+ /*
+ * If first time through, create a plan for this expression, and then see
+ * if we can pass the target variable as a read-write parameter to the
+ * expression. (This is a bit messy, but it seems cleaner than modifying
+ * the API of exec_eval_expr for the purpose.)
+ */
+ if (expr->plan == NULL)
+ {
+ exec_prepare_plan(estate, expr, 0);
+ if (target->dtype == PLPGSQL_DTYPE_VAR)
+ exec_check_rw_parameter(expr, target->dno);
+ }
+
value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod);
exec_assign_value(estate, target, value, isnull, valtype, valtypmod);
exec_eval_cleanup(estate);
/*
* If type is by-reference, copy the new value (which is
* probably in the eval_econtext) into the procedure's memory
- * context.
+ * context. But if it's a read/write reference to an expanded
+ * object, no physical copy needs to happen; at most we need
+ * to reparent the object's memory context.
+ *
+ * If it's an array, we force the value to be stored in R/W
+ * expanded form. This wins if the function later does, say,
+ * a lot of array subscripting operations on the variable, and
+ * otherwise might lose. We might need to use a different
+ * heuristic, but it's too soon to tell. Also, are there
+ * cases where it'd be useful to force non-array values into
+ * expanded form?
*/
if (!var->datatype->typbyval && !isNull)
- newvalue = datumCopy(newvalue,
- false,
- var->datatype->typlen);
+ {
+ if (var->datatype->typisarray &&
+ !VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(newvalue)))
+ {
+ /* array and not already R/W, so apply expand_array */
+ newvalue = expand_array(newvalue,
+ CurrentMemoryContext,
+ NULL);
+ }
+ else
+ {
+ /* else transfer value if R/W, else just datumCopy */
+ newvalue = datumTransfer(newvalue,
+ false,
+ var->datatype->typlen);
+ }
+ }
/*
- * Now free the old value. (We can't do this any earlier
- * because of the possibility that we are assigning the var's
- * old value to it, eg "foo := foo". We could optimize out
- * the assignment altogether in such cases, but it's too
- * infrequent to be worth testing for.)
+ * Now free the old value, unless it's the same as the new
+ * value (ie, we're doing "foo := foo"). Note that for
+ * expanded objects, this test is necessary and cannot
+ * reliably be made any earlier; we have to be looking at the
+ * object's standard R/W pointer to be sure pointer equality
+ * is meaningful.
*/
- free_var(var);
+ if (var->value != newvalue || var->isnull || isNull)
+ free_var(var);
var->value = newvalue;
var->isnull = isNull;
- if (!var->datatype->typbyval && !isNull)
- var->freeval = true;
+ var->freeval = (!var->datatype->typbyval && !isNull);
break;
}
*
* At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
*
- * NOTE: caller must not modify the returned value, since it points right
- * at the stored value in the case of pass-by-reference datatypes. In some
- * cases we have to palloc a return value, and in such cases we put it into
- * the estate's short-term memory context.
+ * NOTE: the returned Datum points right at the stored value in the case of
+ * pass-by-reference datatypes. Generally callers should take care not to
+ * modify the stored value. Some callers intentionally manipulate variables
+ * referenced by R/W expanded pointers, though; it is those callers'
+ * responsibility that the results are semantically OK.
+ *
+ * In some cases we have to palloc a return value, and in such cases we put
+ * it into the estate's short-term memory context.
*/
static void
exec_eval_datum(PLpgSQL_execstate *estate,
{
/* It got replanned ... is it still simple? */
exec_simple_recheck_plan(expr, cplan);
+ /* better recheck r/w safety, as well */
+ if (expr->rwparam >= 0)
+ exec_check_rw_parameter(expr, expr->rwparam);
if (expr->expr_simple_expr == NULL)
{
/* Ooops, release refcount and fail */
*/
MemSet(paramLI->params, 0, estate->ndatums * sizeof(ParamExternData));
- /* Instantiate values for "safe" parameters of the expression */
+ /*
+ * Instantiate values for "safe" parameters of the expression. One of
+ * them might be the variable the expression result will be assigned
+ * to, in which case we can pass the variable's value as-is even if
+ * it's a read-write expanded object; otherwise, convert read-write
+ * pointers to read-only pointers for safety.
+ */
dno = -1;
while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
{
PLpgSQL_var *var = (PLpgSQL_var *) datum;
ParamExternData *prm = ¶mLI->params[dno];
- prm->value = var->value;
+ if (dno == expr->rwparam)
+ prm->value = var->value;
+ else
+ prm->value = MakeExpandedObjectReadOnly(var->value,
+ var->isnull,
+ var->datatype->typlen);
prm->isnull = var->isnull;
prm->pflags = PARAM_FLAG_CONST;
prm->ptype = var->datatype->typoid;
exec_eval_datum(estate, datum,
&prm->ptype, &prmtypmod,
&prm->value, &prm->isnull);
+
+ /*
+ * If it's a read/write expanded datum, convert reference to read-only,
+ * unless it's safe to pass as read-write.
+ */
+ if (datum->dtype == PLPGSQL_DTYPE_VAR && dno != expr->rwparam)
+ prm->value = MakeExpandedObjectReadOnly(prm->value,
+ prm->isnull,
+ ((PLpgSQL_var *) datum)->datatype->typlen);
}
expr->expr_simple_typmod = exprTypmod((Node *) tle->expr);
}
+/*
+ * exec_check_rw_parameter --- can we pass expanded object as read/write param?
+ *
+ * If we have an assignment like "x := array_append(x, foo)" in which the
+ * top-level function is trusted not to corrupt its argument in case of an
+ * error, then when x has an expanded object as value, it is safe to pass the
+ * value as a read/write pointer and let the function modify the value
+ * in-place.
+ *
+ * This function checks for a safe expression, and sets expr->rwparam to the
+ * dno of the target variable (x) if safe, or -1 if not safe.
+ */
+static void
+exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
+{
+ Oid funcid;
+ List *fargs;
+ ListCell *lc;
+
+ /* Assume unsafe */
+ expr->rwparam = -1;
+
+ /*
+ * If the expression isn't simple, there's no point in trying to optimize
+ * (because the exec_run_select code path will flatten any expanded result
+ * anyway). Even without that, this seems like a good safety restriction.
+ */
+ if (expr->expr_simple_expr == NULL)
+ return;
+
+ /*
+ * If target variable isn't referenced by expression, no need to look
+ * further.
+ */
+ if (!bms_is_member(target_dno, expr->paramnos))
+ return;
+
+ /*
+ * Top level of expression must be a simple FuncExpr or OpExpr.
+ */
+ if (IsA(expr->expr_simple_expr, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) expr->expr_simple_expr;
+
+ funcid = fexpr->funcid;
+ fargs = fexpr->args;
+ }
+ else if (IsA(expr->expr_simple_expr, OpExpr))
+ {
+ OpExpr *opexpr = (OpExpr *) expr->expr_simple_expr;
+
+ funcid = opexpr->opfuncid;
+ fargs = opexpr->args;
+ }
+ else
+ return;
+
+ /*
+ * The top-level function must be one that we trust to be "safe".
+ * Currently we hard-wire the list, but it would be very desirable to
+ * allow extensions to mark their functions as safe ...
+ */
+ if (!(funcid == F_ARRAY_APPEND ||
+ funcid == F_ARRAY_PREPEND))
+ return;
+
+ /*
+ * The target variable (in the form of a Param) must only appear as a
+ * direct argument of the top-level function.
+ */
+ foreach(lc, fargs)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ /* A Param is OK, whether it's the target variable or not */
+ if (arg && IsA(arg, Param))
+ continue;
+ /* Otherwise, argument expression must not reference target */
+ if (contains_target_param(arg, &target_dno))
+ return;
+ }
+
+ /* OK, we can pass target as a read-write parameter */
+ expr->rwparam = target_dno;
+}
+
+/*
+ * Recursively check for a Param referencing the target variable
+ */
+static bool
+contains_target_param(Node *node, int *target_dno)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ if (param->paramkind == PARAM_EXTERN &&
+ param->paramid == *target_dno + 1)
+ return true;
+ return false;
+ }
+ return expression_tree_walker(node, contains_target_param,
+ (void *) target_dno);
+}
+
/* ----------
* exec_set_found Set the global found variable to true/false
* ----------
{
if (var->freeval)
{
- pfree(DatumGetPointer(var->value));
+ if (DatumIsReadWriteExpandedObject(var->value,
+ var->isnull,
+ var->datatype->typlen))
+ DeleteExpandedObject(var->value);
+ else
+ pfree(DatumGetPointer(var->value));
var->freeval = false;
}
}
curvar = (PLpgSQL_var *) estate->datums[dno];
- exec_eval_datum(estate, (PLpgSQL_datum *) curvar, ¶mtypeid,
- ¶mtypmod, ¶mdatum, ¶misnull);
+ exec_eval_datum(estate, (PLpgSQL_datum *) curvar,
+ ¶mtypeid, ¶mtypmod,
+ ¶mdatum, ¶misnull);
appendStringInfo(¶mstr, "%s%s = ",
paramno > 0 ? ", " : "",
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
char typtype;
Oid typrelid;
Oid collation; /* from pg_type, but can be overridden */
+ bool typisarray; /* is "true" array, or domain over one */
int32 atttypmod; /* typmod (taken from someplace else) */
} PLpgSQL_type;
char *query;
SPIPlanPtr plan;
Bitmapset *paramnos; /* all dnos referenced by this query */
+ int rwparam; /* dno of read/write param, or -1 if none */
/* function containing this expr (not set until we first parse query) */
struct PLpgSQL_function *func;