Support "expanded" objects, particularly arrays, for better performance.

This patch introduces the ability for complex datatypes to have an
in-memory representation that is different from their on-disk format.
On-disk formats are typically optimized for minimal size, and in any case
they can't contain pointers, so they are often not well-suited for
computation.  Now a datatype can invent an "expanded" in-memory format
that is better suited for its operations, and then pass that around among
the C functions that operate on the datatype.  There are also provisions
(rudimentary as yet) to allow an expanded object to be modified in-place
under suitable conditions, so that operations like assignment to an element
of an array need not involve copying the entire array.

The initial application for this feature is arrays, but it is not hard
to foresee using it for other container types like JSON, XML and hstore.
I have hopes that it will be useful to PostGIS as well.

In this initial implementation, a few heuristics have been hard-wired
into plpgsql to improve performance for arrays that are stored in
plpgsql variables.  We would like to generalize those hacks so that
other datatypes can obtain similar improvements, but figuring out some
appropriate APIs is left as a task for future work.  (The heuristics
themselves are probably not optimal yet, either, as they sometimes
force expansion of arrays that would be better left alone.)

Preliminary performance testing shows impressive speed gains for plpgsql
functions that do element-by-element access or update of large arrays.
There are other cases that get a little slower, as a result of added array
format conversions; but we can hope to improve anything that's annoyingly
bad.  In any case most applications should see a net win.

Tom Lane, reviewed by Andres Freund
This commit is contained in:
Tom Lane 2015-05-14 12:08:40 -04:00
parent 8a2e1edd2b
commit 1dc5ebc907
27 changed files with 2362 additions and 526 deletions

View File

@ -503,8 +503,9 @@ comparison table, in which all the HTML pages were cut down to 7 kB to fit.
<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>
@ -518,6 +519,43 @@ that the referenced data survives for as long as the pointer could exist,
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

View File

@ -300,6 +300,77 @@ CREATE TYPE complex (
</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>

View File

@ -60,6 +60,7 @@
#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? */
@ -93,13 +94,15 @@ heap_compute_data_size(TupleDesc tupleDesc,
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)))
{
/*
@ -108,11 +111,21 @@ heap_compute_data_size(TupleDesc tupleDesc,
*/
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);
}
}
@ -203,10 +216,26 @@ heap_fill_tuple(TupleDesc tupleDesc,
*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))
{

View File

@ -37,6 +37,7 @@
#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"
@ -130,6 +131,19 @@ heap_tuple_fetch_attr(struct varlena * attr)
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
{
/*
@ -196,6 +210,15 @@ heap_tuple_untoast_attr(struct varlena * attr)
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))
{
/*
@ -263,6 +286,11 @@ heap_tuple_untoast_attr_slice(struct varlena * 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;
@ -344,6 +372,10 @@ toast_raw_datum_size(Datum value)
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 */
@ -400,6 +432,10 @@ toast_datum_size(Datum value)
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);

View File

@ -4248,7 +4248,6 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
Datum result;
ArrayType *array;
FunctionCallInfoData locfcinfo;
result = ExecEvalExpr(astate->arg, econtext, isNull, isDone);
@ -4265,14 +4264,12 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
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)
{
@ -4302,15 +4299,14 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
*/
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);
}
/* ----------------------------------------------------------------

View File

@ -88,6 +88,7 @@
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "utils/builtins.h"
#include "utils/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
@ -812,6 +813,52 @@ ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
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

View File

@ -56,7 +56,15 @@ SubqueryNext(SubqueryScanState *node)
* 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;
}

View File

@ -1015,6 +1015,27 @@ SPI_pfree(void *pointer)
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)
{

View File

@ -16,10 +16,11 @@ endif
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 \

View File

@ -0,0 +1,455 @@
/*-------------------------------------------------------------------------
*
* 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);
}
}

View File

@ -25,22 +25,36 @@ static Datum array_position_common(FunctionCallInfo fcinfo);
/*
* 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
{
@ -57,30 +71,12 @@ fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("input data type is not an array")));
v = construct_empty_array(element_type);
eah = construct_empty_expanded_array(element_type,
CurrentMemoryContext,
my_extra);
}
/* 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;
}
return v;
return eah;
}
/*-----------------------------------------------------------------------------
@ -91,29 +87,29 @@ fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
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;
@ -123,7 +119,7 @@ array_append(PG_FUNCTION_ARGS)
(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,
@ -133,10 +129,11 @@ array_append(PG_FUNCTION_ARGS)
/* 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);
}
/*-----------------------------------------------------------------------------
@ -147,12 +144,13 @@ array_append(PG_FUNCTION_ARGS)
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);
@ -160,13 +158,14 @@ array_prepend(PG_FUNCTION_ARGS)
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])
@ -174,8 +173,11 @@ array_prepend(PG_FUNCTION_ARGS)
(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),
@ -184,14 +186,19 @@ array_prepend(PG_FUNCTION_ARGS)
/* 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);
}
/*-----------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -12,8 +12,9 @@
*
*-------------------------------------------------------------------------
*/
/*
* 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
@ -34,11 +35,15 @@
*
* 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"
/*-------------------------------------------------------------------------
@ -46,6 +51,7 @@
*
* 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.
@ -106,9 +112,16 @@ datumGetSize(Datum value, bool typByVal, int typLen)
/*-------------------------------------------------------------------------
* 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
@ -118,44 +131,71 @@ datumCopy(Datum value, bool typByVal, int typLen)
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

View File

@ -0,0 +1,163 @@
/*-------------------------------------------------------------------------
*
* 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);
}

View File

@ -323,6 +323,10 @@ MemoryContextSetParent(MemoryContext context, MemoryContext new_parent)
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)
{

View File

@ -124,6 +124,7 @@ extern char *SPI_getnspname(Relation rel);
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);

View File

@ -163,6 +163,7 @@ extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
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);

View File

@ -309,6 +309,10 @@ typedef struct WindowFunc
* 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

View File

@ -87,6 +87,23 @@ typedef struct varatt_indirect
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
@ -95,11 +112,18 @@ typedef struct varatt_indirect
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"))
@ -294,6 +318,12 @@ typedef struct
(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))

View File

@ -45,6 +45,11 @@
* 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
@ -57,6 +62,8 @@
#define ARRAY_H
#include "fmgr.h"
#include "utils/expandeddatum.h"
/*
* Arrays are varlena objects, so must meet the varlena convention that
@ -74,6 +81,86 @@ typedef struct
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)
@ -151,17 +238,24 @@ typedef struct ArrayMapState
/* 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).
@ -209,6 +303,22 @@ typedef struct ArrayIteratorData *ArrayIterator;
#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
@ -250,6 +360,15 @@ extern Datum array_remove(PG_FUNCTION_ARGS);
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);
@ -271,7 +390,7 @@ extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
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,
@ -288,6 +407,9 @@ extern ArrayType *construct_md_array(Datum *elems,
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,
@ -340,6 +462,17 @@ extern void mda_get_offset_values(int n, int *dist, const int *prod, const int *
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
*/

View File

@ -0,0 +1,133 @@
/*-------------------------------------------------------------------------
*
* 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 */

View File

@ -24,18 +24,18 @@
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

View File

@ -0,0 +1,151 @@
/*-------------------------------------------------------------------------
*
* 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 */

View File

@ -2200,6 +2200,22 @@ build_datatype(HeapTuple typeTup, int32 typmod, Oid collation)
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;

View File

@ -34,6 +34,7 @@
#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"
@ -173,6 +174,8 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
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,
@ -312,6 +315,44 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
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;
@ -477,18 +518,14 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
/*
* 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);
}
}
@ -2476,6 +2513,13 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
* 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)
{
@ -2604,6 +2648,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
* 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)
{
@ -2622,6 +2671,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
(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,
@ -3333,6 +3387,13 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
/* 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;
}
@ -4071,6 +4132,19 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
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);
@ -4140,26 +4214,51 @@ exec_assign_value(PLpgSQL_execstate *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;
}
@ -4505,10 +4604,14 @@ exec_assign_value(PLpgSQL_execstate *estate,
*
* 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,
@ -5216,6 +5319,9 @@ exec_eval_simple_expr(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 */
@ -5362,7 +5468,13 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
*/
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)
{
@ -5373,7 +5485,12 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
PLpgSQL_var *var = (PLpgSQL_var *) datum;
ParamExternData *prm = &paramLI->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;
@ -5442,6 +5559,15 @@ plpgsql_param_fetch(ParamListInfo params, int paramid)
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);
}
@ -6384,6 +6510,113 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
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
* ----------
@ -6540,7 +6773,12 @@ free_var(PLpgSQL_var *var)
{
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;
}
}
@ -6750,8 +6988,9 @@ format_expr_params(PLpgSQL_execstate *estate,
curvar = (PLpgSQL_var *) estate->datums[dno];
exec_eval_datum(estate, (PLpgSQL_datum *) curvar, &paramtypeid,
&paramtypmod, &paramdatum, &paramisnull);
exec_eval_datum(estate, (PLpgSQL_datum *) curvar,
&paramtypeid, &paramtypmod,
&paramdatum, &paramisnull);
appendStringInfo(&paramstr, "%s%s = ",
paramno > 0 ? ", " : "",

View File

@ -2625,6 +2625,7 @@ read_sql_construct(int until,
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
@ -2849,6 +2850,7 @@ make_execsql_stmt(int firsttoken, int location)
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
@ -3732,6 +3734,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);

View File

@ -183,6 +183,7 @@ typedef struct
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;
@ -216,6 +217,7 @@ typedef struct PLpgSQL_expr
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;