Re: Manipulating complex types as non-contiguous structures in-memory - Mailing list pgsql-hackers
| From | Tom Lane |
|---|---|
| Subject | Re: Manipulating complex types as non-contiguous structures in-memory |
| Date | |
| Msg-id | 29085.1430866213@sss.pgh.pa.us Whole thread Raw |
| In response to | Re: Manipulating complex types as non-contiguous structures in-memory (Tom Lane <tgl@sss.pgh.pa.us>) |
| Responses |
Re: Manipulating complex types as non-contiguous structures in-memory
Re: Manipulating complex types as non-contiguous structures in-memory |
| List | pgsql-hackers |
I wrote:
> Pavel Stehule <pavel.stehule@gmail.com> writes:
>> Significant slowdown is on following test:
>> do $$ declare a int[] := '{}'; begin for i in 1..90000 loop a := a || 10;
>> end loop; end$$ language plpgsql;
>> do $$ declare a numeric[] := '{}'; begin for i in 1..90000 loop a := a ||
>> 10.1; end loop; end$$ language plpgsql;
>> integer master 14sec x patched 55sec
>> numeric master 43sec x patched 108sec
>> It is probably worst case - and it is known plpgsql antipattern
> Yeah, I have not expended a great deal of effort on the array_append/
> array_prepend/array_cat code paths. Still, in these plpgsql cases,
> we should in principle have gotten down from two array copies per loop to
> one, so it's disappointing to not have better results there, even granting
> that the new "copy" step is not just a byte-by-byte copy. Let me see if
> there's anything simple to be done about that.
The attached updated patch reduces both of those do-loop tests to about
60 msec on my machine. It contains two improvements over the 1.1 patch:
1. There's a fast path for copying an expanded array to another expanded
array when the element type is pass-by-value: we can just memcpy the
Datum array instead of working element-by-element. In isolation, that
change made the patch a little faster than 9.4 on your int-array case,
though of course it doesn't help for the numeric-array case (and I do not
see a way to avoid working element-by-element for pass-by-ref cases).
2. pl/pgsql now detects cases like "a := a || x" and allows the array "a"
to be passed as a read-write pointer to array_append, so that array_append
can modify expanded arrays in-place and avoid inessential data copying
altogether. (The earlier patch had made array_append and array_prepend
safe for this usage, but there wasn't actually any way to invoke them
with read-write pointers.) I had speculated about doing this in my
earliest discussion of this patch, but there was no code for it before.
The key question for change #2 is how do we identify what is a "safe"
top-level function that can be trusted not to corrupt the read-write value
if it fails partway through. I did not have a good answer before, and
I still don't; what this version of the patch does is to hard-wire
array_append and array_prepend as the functions considered safe.
Obviously that is crying out for improvement, but we can leave that
question for later; at least now we have infrastructure that makes it
possible to do it.
Change #1 is actually not relevant to these example cases, because we
don't copy any arrays within the loop given change #2. But I left it in
because it's not much code and it will help for situations where change #2
doesn't apply.
regards, tom lane
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index d8c5287..e5b7b4b 100644
*** a/doc/src/sgml/storage.sgml
--- b/doc/src/sgml/storage.sgml
*************** comparison table, in which all the HTML
*** 503,510 ****
<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.
</para>
<para>
--- 503,511 ----
<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
! 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 w
*** 519,524 ****
--- 520,562 ----
</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
stored on disk. In-memory <acronym>TOAST</> pointers are automatically
diff --git a/doc/src/sgml/xtypes.sgml b/doc/src/sgml/xtypes.sgml
index 2459616..ac0b8a2 100644
*** a/doc/src/sgml/xtypes.sgml
--- b/doc/src/sgml/xtypes.sgml
*************** CREATE TYPE complex (
*** 300,305 ****
--- 300,376 ----
</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>
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 6cd4e8e..de7f02f 100644
*** a/src/backend/access/common/heaptuple.c
--- b/src/backend/access/common/heaptuple.c
***************
*** 60,65 ****
--- 60,66 ----
#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? */
*************** heap_compute_data_size(TupleDesc tupleDe
*** 93,105 ****
for (i = 0; i < numberOfAttributes; i++)
{
Datum val;
if (isnull[i])
continue;
val = values[i];
! if (ATT_IS_PACKABLE(att[i]) &&
VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
{
/*
--- 94,108 ----
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(atti) &&
VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
{
/*
*************** heap_compute_data_size(TupleDesc tupleDe
*** 108,118 ****
*/
data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(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,
val);
}
}
--- 111,131 ----
*/
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, atti->attalign,
! atti->attlen, val);
! data_length = att_addlength_datum(data_length, atti->attlen,
val);
}
}
*************** heap_fill_tuple(TupleDesc tupleDesc,
*** 203,212 ****
*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);
}
else if (VARATT_IS_SHORT(val))
{
--- 216,241 ----
*infomask |= HEAP_HASVARWIDTH;
if (VARATT_IS_EXTERNAL(val))
{
! 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))
{
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 8464e87..c3ebbef 100644
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
***************
*** 37,42 ****
--- 37,43 ----
#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"
*************** heap_tuple_fetch_attr(struct varlena * a
*** 130,135 ****
--- 131,149 ----
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
{
/*
*************** heap_tuple_untoast_attr(struct varlena *
*** 196,201 ****
--- 210,224 ----
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))
{
/*
*************** heap_tuple_untoast_attr_slice(struct var
*** 263,268 ****
--- 286,296 ----
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;
*************** toast_raw_datum_size(Datum value)
*** 344,349 ****
--- 372,381 ----
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 */
*************** toast_datum_size(Datum value)
*** 400,405 ****
--- 432,441 ----
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);
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index d94fe58..e599411 100644
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
*************** ExecEvalArrayCoerceExpr(ArrayCoerceExprS
*** 4248,4254 ****
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
Datum result;
- ArrayType *array;
FunctionCallInfoData locfcinfo;
result = ExecEvalExpr(astate->arg, econtext, isNull, isDone);
--- 4248,4253 ----
*************** ExecEvalArrayCoerceExpr(ArrayCoerceExprS
*** 4265,4278 ****
if (!OidIsValid(acoerce->elemfuncid))
{
/* Detoast input array if necessary, and copy in any case */
! 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)
{
--- 4264,4275 ----
if (!OidIsValid(acoerce->elemfuncid))
{
/* Detoast input array if necessary, and copy in any case */
! ArrayType *array = DatumGetArrayTypePCopy(result);
!
ARR_ELEMTYPE(array) = astate->resultelemtype;
PG_RETURN_ARRAYTYPE_P(array);
}
/* Initialize function cache if first time through */
if (astate->elemfunc.fn_oid == InvalidOid)
{
*************** ExecEvalArrayCoerceExpr(ArrayCoerceExprS
*** 4302,4316 ****
*/
InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
InvalidOid, NULL, NULL);
! locfcinfo.arg[0] = PointerGetDatum(array);
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);
}
/* ----------------------------------------------------------------
--- 4299,4312 ----
*/
InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
InvalidOid, NULL, NULL);
! 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, astate->resultelemtype, astate->amstate);
}
/* ----------------------------------------------------------------
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 753754d..a05d8b1 100644
*** a/src/backend/executor/execTuples.c
--- b/src/backend/executor/execTuples.c
***************
*** 88,93 ****
--- 88,94 ----
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "utils/builtins.h"
+ #include "utils/expandeddatum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
*************** ExecCopySlot(TupleTableSlot *dstslot, Tu
*** 812,817 ****
--- 813,864 ----
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
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 3f66e24..e5d1e54 100644
*** a/src/backend/executor/nodeSubqueryscan.c
--- b/src/backend/executor/nodeSubqueryscan.c
*************** SubqueryNext(SubqueryScanState *node)
*** 56,62 ****
--- 56,70 ----
* 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;
}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 557d153..472de41 100644
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
*************** SPI_pfree(void *pointer)
*** 1015,1020 ****
--- 1015,1041 ----
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)
{
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 1f1bee7..3ed0b44 100644
*** a/src/backend/utils/adt/Makefile
--- b/src/backend/utils/adt/Makefile
*************** endif
*** 16,25 ****
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 \
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 \
--- 16,26 ----
endif
# keep this list arranged alphabetically or it gets to be a mess
! 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 \
diff --git a/src/backend/utils/adt/array_expanded.c b/src/backend/utils/adt/array_expanded.c
index ...97fd444 .
*** a/src/backend/utils/adt/array_expanded.c
--- b/src/backend/utils/adt/array_expanded.c
***************
*** 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);
+ }
+ }
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 4177d2d..f7b57da 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
*************** static Datum array_position_common(Funct
*** 25,46 ****
/*
* 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.
*/
! static ArrayType *
fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
{
! ArrayType *v;
Oid element_type;
ArrayMetaState *my_extra;
! /* First collect the array value */
if (!PG_ARGISNULL(argno))
{
! v = PG_GETARG_ARRAYTYPE_P(argno);
! element_type = ARR_ELEMTYPE(v);
}
else
{
--- 25,60 ----
/*
* fetch_array_arg_replace_nulls
*
! * 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 ExpandedArrayHeader *
fetch_array_arg_replace_nulls(FunctionCallInfo fcinfo, int argno)
{
! ExpandedArrayHeader *eah;
Oid element_type;
ArrayMetaState *my_extra;
! /* 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))
{
! eah = PG_GETARG_EXPANDED_ARRAYX(argno, my_extra);
}
else
{
*************** fetch_array_arg_replace_nulls(FunctionCa
*** 57,86 ****
(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;
}
! return v;
}
/*-----------------------------------------------------------------------------
--- 71,82 ----
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("input data type is not an array")));
! eah = construct_empty_expanded_array(element_type,
! CurrentMemoryContext,
! my_extra);
}
! return eah;
}
/*-----------------------------------------------------------------------------
*************** fetch_array_arg_replace_nulls(FunctionCa
*** 91,119 ****
Datum
array_append(PG_FUNCTION_ARGS)
{
! ArrayType *v;
Datum newelem;
bool isNull;
! ArrayType *result;
int *dimv,
*lb;
int indx;
ArrayMetaState *my_extra;
! v = 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)
{
/* append newelem */
int ub;
! lb = ARR_LBOUND(v);
! dimv = ARR_DIMS(v);
ub = dimv[0] + lb[0] - 1;
indx = ub + 1;
--- 87,115 ----
Datum
array_append(PG_FUNCTION_ARGS)
{
! ExpandedArrayHeader *eah;
Datum newelem;
bool isNull;
! Datum result;
int *dimv,
*lb;
int indx;
ArrayMetaState *my_extra;
! eah = fetch_array_arg_replace_nulls(fcinfo, 0);
isNull = PG_ARGISNULL(1);
if (isNull)
newelem = (Datum) 0;
else
newelem = PG_GETARG_DATUM(1);
! if (eah->ndims == 1)
{
/* append newelem */
int ub;
! lb = eah->lbound;
! dimv = eah->dims;
ub = dimv[0] + lb[0] - 1;
indx = ub + 1;
*************** array_append(PG_FUNCTION_ARGS)
*** 123,129 ****
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
! else if (ARR_NDIM(v) == 0)
indx = 1;
else
ereport(ERROR,
--- 119,125 ----
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
! else if (eah->ndims == 0)
indx = 1;
else
ereport(ERROR,
*************** array_append(PG_FUNCTION_ARGS)
*** 133,142 ****
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
! result = array_set(v, 1, &indx, newelem, isNull,
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
! PG_RETURN_ARRAYTYPE_P(result);
}
/*-----------------------------------------------------------------------------
--- 129,139 ----
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
! result = array_set_element(EOHPGetRWDatum(&eah->hdr),
! 1, &indx, newelem, isNull,
-1, my_extra->typlen, my_extra->typbyval, my_extra->typalign);
! PG_RETURN_DATUM(result);
}
/*-----------------------------------------------------------------------------
*************** array_append(PG_FUNCTION_ARGS)
*** 147,158 ****
Datum
array_prepend(PG_FUNCTION_ARGS)
{
! ArrayType *v;
Datum newelem;
bool isNull;
! ArrayType *result;
int *lb;
int indx;
ArrayMetaState *my_extra;
isNull = PG_ARGISNULL(0);
--- 144,156 ----
Datum
array_prepend(PG_FUNCTION_ARGS)
{
! ExpandedArrayHeader *eah;
Datum newelem;
bool isNull;
! Datum result;
int *lb;
int indx;
+ int lb0;
ArrayMetaState *my_extra;
isNull = PG_ARGISNULL(0);
*************** array_prepend(PG_FUNCTION_ARGS)
*** 160,172 ****
newelem = (Datum) 0;
else
newelem = PG_GETARG_DATUM(0);
! v = fetch_array_arg_replace_nulls(fcinfo, 1);
! if (ARR_NDIM(v) == 1)
{
/* prepend newelem */
! lb = ARR_LBOUND(v);
indx = lb[0] - 1;
/* overflow? */
if (indx > lb[0])
--- 158,171 ----
newelem = (Datum) 0;
else
newelem = PG_GETARG_DATUM(0);
! eah = fetch_array_arg_replace_nulls(fcinfo, 1);
! if (eah->ndims == 1)
{
/* prepend newelem */
! lb = eah->lbound;
indx = lb[0] - 1;
+ lb0 = lb[0];
/* overflow? */
if (indx > lb[0])
*************** array_prepend(PG_FUNCTION_ARGS)
*** 174,181 ****
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
! else if (ARR_NDIM(v) == 0)
indx = 1;
else
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
--- 173,183 ----
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
! else if (eah->ndims == 0)
! {
indx = 1;
+ lb0 = 1;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATA_EXCEPTION),
*************** array_prepend(PG_FUNCTION_ARGS)
*** 184,197 ****
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
! result = array_set(v, 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];
! PG_RETURN_ARRAYTYPE_P(result);
}
/*-----------------------------------------------------------------------------
--- 186,204 ----
/* Perform element insertion */
my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
! 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 */
! Assert(result == EOHPGetRWDatum(&eah->hdr));
! if (eah->ndims == 1)
! {
! /* This is ok whether we've deconstructed or not */
! eah->lbound[0] = lb0;
! }
! PG_RETURN_DATUM(result);
}
/*-----------------------------------------------------------------------------
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 9117a55..26fa648 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
*************** bool Array_nulls = true;
*** 42,47 ****
--- 42,53 ----
*/
#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,
*************** static void ReadArrayBinary(StringInfo b
*** 93,102 ****
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 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);
--- 99,114 ----
int typlen, bool typbyval, char typalign,
Datum *values, bool *nulls,
bool *hasnulls, int32 *nbytes);
! 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);
*************** ReadArrayStr(char *arrayStr,
*** 939,945 ****
* the values are not toasted. (Doing it here doesn't work since the
* caller has already allocated space for the array...)
*/
! static void
CopyArrayEls(ArrayType *array,
Datum *values,
bool *nulls,
--- 951,957 ----
* the values are not toasted. (Doing it here doesn't work since the
* caller has already allocated space for the array...)
*/
! void
CopyArrayEls(ArrayType *array,
Datum *values,
bool *nulls,
*************** CopyArrayEls(ArrayType *array,
*** 997,1004 ****
Datum
array_out(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
! Oid element_type = ARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
--- 1009,1016 ----
Datum
array_out(PG_FUNCTION_ARGS)
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
! Oid element_type = AARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
*************** array_out(PG_FUNCTION_ARGS)
*** 1014,1021 ****
*
* +2 allows for assignment operator + trailing null
*/
- bits8 *bitmap;
- int bitmask;
bool *needquotes,
needdims = false;
int nitems,
--- 1026,1031 ----
*************** array_out(PG_FUNCTION_ARGS)
*** 1027,1032 ****
--- 1037,1043 ----
int ndim,
*dims,
*lb;
+ ARRAY_ITER ARRAY_ITER_VARS(iter);
ArrayMetaState *my_extra;
/*
*************** array_out(PG_FUNCTION_ARGS)
*** 1061,1069 ****
typalign = my_extra->typalign;
typdelim = my_extra->typdelim;
! ndim = ARR_NDIM(v);
! dims = ARR_DIMS(v);
! lb = ARR_LBOUND(v);
nitems = ArrayGetNItems(ndim, dims);
if (nitems == 0)
--- 1072,1080 ----
typalign = my_extra->typalign;
typdelim = my_extra->typdelim;
! ndim = AARR_NDIM(v);
! dims = AARR_DIMS(v);
! lb = AARR_LBOUND(v);
nitems = ArrayGetNItems(ndim, dims);
if (nitems == 0)
*************** array_out(PG_FUNCTION_ARGS)
*** 1094,1109 ****
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;
for (i = 0; i < nitems; i++)
{
bool needquote;
/* Get source element, checking for NULL */
! if (bitmap && (*bitmap & bitmask) == 0)
{
values[i] = pstrdup("NULL");
overall_length += 4;
--- 1105,1122 ----
needquotes = (bool *) palloc(nitems * sizeof(bool));
overall_length = 1; /* don't forget to count \0 at end. */
! ARRAY_ITER_SETUP(iter, v);
for (i = 0; i < nitems; i++)
{
+ Datum itemvalue;
+ bool isnull;
bool needquote;
/* Get source element, checking for NULL */
! ARRAY_ITER_NEXT(iter, i, itemvalue, isnull, typlen, typbyval, typalign);
!
! if (isnull)
{
values[i] = pstrdup("NULL");
overall_length += 4;
*************** array_out(PG_FUNCTION_ARGS)
*** 1111,1122 ****
}
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')
--- 1124,1130 ----
*************** array_out(PG_FUNCTION_ARGS)
*** 1149,1165 ****
overall_length += 2;
/* and the comma */
overall_length += 1;
-
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
- }
}
/*
--- 1157,1162 ----
*************** ReadArrayBinary(StringInfo buf,
*** 1534,1552 ****
Datum
array_send(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
! Oid element_type = ARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
- char *p;
- bits8 *bitmap;
- int bitmask;
int nitems,
i;
int ndim,
! *dim;
StringInfoData buf;
ArrayMetaState *my_extra;
/*
--- 1531,1548 ----
Datum
array_send(PG_FUNCTION_ARGS)
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
! Oid element_type = AARR_ELEMTYPE(v);
int typlen;
bool typbyval;
char typalign;
int nitems,
i;
int ndim,
! *dim,
! *lb;
StringInfoData buf;
+ ARRAY_ITER ARRAY_ITER_VARS(iter);
ArrayMetaState *my_extra;
/*
*************** array_send(PG_FUNCTION_ARGS)
*** 1583,1642 ****
typbyval = my_extra->typbyval;
typalign = my_extra->typalign;
! ndim = ARR_NDIM(v);
! dim = ARR_DIMS(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, 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);
}
/* Send the array elements using the element's own sendproc */
! p = ARR_DATA_PTR(v);
! bitmap = ARR_NULLBITMAP(v);
! bitmask = 1;
for (i = 0; i < nitems; i++)
{
/* Get source element, checking for NULL */
! if (bitmap && (*bitmap & bitmask) == 0)
{
/* -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;
- }
}
}
--- 1579,1626 ----
typbyval = my_extra->typbyval;
typalign = my_extra->typalign;
! 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, AARR_HASNULL(v) ? 1 : 0, 4);
pq_sendint(&buf, element_type, sizeof(Oid));
for (i = 0; i < ndim; i++)
{
! pq_sendint(&buf, dim[i], 4);
! pq_sendint(&buf, lb[i], 4);
}
/* Send the array elements using the element's own sendproc */
! ARRAY_ITER_SETUP(iter, v);
for (i = 0; i < nitems; i++)
{
+ Datum itemvalue;
+ bool isnull;
+
/* Get source element, checking for NULL */
! ARRAY_ITER_NEXT(iter, i, itemvalue, isnull, typlen, typbyval, typalign);
!
! if (isnull)
{
/* -1 length means a NULL */
pq_sendint(&buf, -1, 4);
}
else
{
bytea *outputbytes;
outputbytes = SendFunctionCall(&my_extra->proc, itemvalue);
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
pq_sendbytes(&buf, VARDATA(outputbytes),
VARSIZE(outputbytes) - VARHDRSZ);
pfree(outputbytes);
}
}
*************** array_send(PG_FUNCTION_ARGS)
*** 1650,1662 ****
Datum
array_ndims(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
/* Sanity check: does it look like an array at all? */
! if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
! PG_RETURN_INT32(ARR_NDIM(v));
}
/*
--- 1634,1646 ----
Datum
array_ndims(PG_FUNCTION_ARGS)
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
/* Sanity check: does it look like an array at all? */
! if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
! PG_RETURN_INT32(AARR_NDIM(v));
}
/*
*************** array_ndims(PG_FUNCTION_ARGS)
*** 1666,1672 ****
Datum
array_dims(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
char *p;
int i;
int *dimv,
--- 1650,1656 ----
Datum
array_dims(PG_FUNCTION_ARGS)
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
char *p;
int i;
int *dimv,
*************** array_dims(PG_FUNCTION_ARGS)
*** 1680,1693 ****
char buf[MAXDIM * 33 + 1];
/* Sanity check: does it look like an array at all? */
! if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
! dimv = ARR_DIMS(v);
! lb = ARR_LBOUND(v);
p = buf;
! for (i = 0; i < ARR_NDIM(v); i++)
{
sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1);
p += strlen(p);
--- 1664,1677 ----
char buf[MAXDIM * 33 + 1];
/* Sanity check: does it look like an array at all? */
! if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
! dimv = AARR_DIMS(v);
! lb = AARR_LBOUND(v);
p = buf;
! for (i = 0; i < AARR_NDIM(v); i++)
{
sprintf(p, "[%d:%d]", lb[i], dimv[i] + lb[i] - 1);
p += strlen(p);
*************** array_dims(PG_FUNCTION_ARGS)
*** 1704,1723 ****
Datum
array_lower(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(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)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > ARR_NDIM(v))
PG_RETURN_NULL();
! lb = ARR_LBOUND(v);
result = lb[reqdim - 1];
PG_RETURN_INT32(result);
--- 1688,1707 ----
Datum
array_lower(PG_FUNCTION_ARGS)
{
! 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 (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
! lb = AARR_LBOUND(v);
result = lb[reqdim - 1];
PG_RETURN_INT32(result);
*************** array_lower(PG_FUNCTION_ARGS)
*** 1731,1752 ****
Datum
array_upper(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(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)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > ARR_NDIM(v))
PG_RETURN_NULL();
! lb = ARR_LBOUND(v);
! dimv = ARR_DIMS(v);
result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
--- 1715,1736 ----
Datum
array_upper(PG_FUNCTION_ARGS)
{
! 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 (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
! lb = AARR_LBOUND(v);
! dimv = AARR_DIMS(v);
result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
*************** array_upper(PG_FUNCTION_ARGS)
*** 1761,1780 ****
Datum
array_length(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(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)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > ARR_NDIM(v))
PG_RETURN_NULL();
! dimv = ARR_DIMS(v);
result = dimv[reqdim - 1];
--- 1745,1764 ----
Datum
array_length(PG_FUNCTION_ARGS)
{
! 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 (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
PG_RETURN_NULL();
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > AARR_NDIM(v))
PG_RETURN_NULL();
! dimv = AARR_DIMS(v);
result = dimv[reqdim - 1];
*************** array_length(PG_FUNCTION_ARGS)
*** 1788,1796 ****
Datum
array_cardinality(PG_FUNCTION_ARGS)
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
! PG_RETURN_INT32(ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)));
}
--- 1772,1780 ----
Datum
array_cardinality(PG_FUNCTION_ARGS)
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
! PG_RETURN_INT32(ArrayGetNItems(AARR_NDIM(v), AARR_DIMS(v)));
}
*************** array_get_element(Datum arraydatum,
*** 1825,1831 ****
char elmalign,
bool *isNull)
{
- ArrayType *array;
int i,
ndim,
*dim,
--- 1809,1814 ----
*************** array_get_element(Datum arraydatum,
*** 1850,1859 ****
arraydataptr = (char *) DatumGetPointer(arraydatum);
arraynullsptr = NULL;
}
else
{
! /* detoast input array if necessary */
! array = DatumGetArrayTypeP(arraydatum);
ndim = ARR_NDIM(array);
dim = ARR_DIMS(array);
--- 1833,1854 ----
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 array if necessary, producing normal varlena input */
! ArrayType *array = DatumGetArrayTypeP(arraydatum);
ndim = ARR_NDIM(array);
dim = ARR_DIMS(array);
*************** array_get_element(Datum arraydatum,
*** 1903,1908 ****
--- 1898,1985 ----
}
/*
+ * 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
* lowerIndx), creates a new array structure for the referred elements
*************** array_get_slice(Datum arraydatum,
*** 2083,2089 ****
*
* Result:
* A new array is returned, just like the old except for the one
! * modified entry. The original array object is not changed.
*
* For one-dimensional arrays only, we allow the array to be extended
* by assigning to a position outside the existing subscript range; any
--- 2160,2168 ----
*
* Result:
* A new array is returned, just like the old except for the one
! * 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
*************** array_set_element(Datum arraydatum,
*** 2166,2171 ****
--- 2245,2264 ----
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);
*************** array_set_element(Datum arraydatum,
*** 2355,2360 ****
--- 2448,2698 ----
}
/*
+ * 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
* by upper and lower subscript values) to new values passed as
*************** array_set(ArrayType *array, int nSubscri
*** 2734,2741 ****
* 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
--- 3072,3077 ----
*************** array_set(ArrayType *array, int nSubscri
*** 2749,2762 ****
* the array are OK however.
*/
Datum
! array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType,
! ArrayMapState *amstate)
{
! ArrayType *v;
ArrayType *result;
Datum *values;
bool *nulls;
- Datum elt;
int *dim;
int ndim;
int nitems;
--- 3085,3096 ----
* the array are OK however.
*/
Datum
! array_map(FunctionCallInfo fcinfo, Oid retType, ArrayMapState *amstate)
{
! AnyArrayType *v;
ArrayType *result;
Datum *values;
bool *nulls;
int *dim;
int ndim;
int nitems;
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2764,2778 ****
int32 nbytes = 0;
int32 dataoffset;
bool hasnulls;
int inp_typlen;
bool inp_typbyval;
char inp_typalign;
int typlen;
bool typbyval;
char typalign;
! char *s;
! bits8 *bitmap;
! int bitmask;
ArrayMetaState *inp_extra;
ArrayMetaState *ret_extra;
--- 3098,3111 ----
int32 nbytes = 0;
int32 dataoffset;
bool hasnulls;
+ Oid inpType;
int inp_typlen;
bool inp_typbyval;
char inp_typalign;
int typlen;
bool typbyval;
char typalign;
! ARRAY_ITER ARRAY_ITER_VARS(iter);
ArrayMetaState *inp_extra;
ArrayMetaState *ret_extra;
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2781,2792 ****
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);
! ndim = ARR_NDIM(v);
! dim = ARR_DIMS(v);
nitems = ArrayGetNItems(ndim, dim);
/* Check for empty array */
--- 3114,3124 ----
elog(ERROR, "invalid nargs: %d", fcinfo->nargs);
if (PG_ARGISNULL(0))
elog(ERROR, "null input array");
! v = PG_GETARG_ANY_ARRAY(0);
! inpType = AARR_ELEMTYPE(v);
! ndim = AARR_NDIM(v);
! dim = AARR_DIMS(v);
nitems = ArrayGetNItems(ndim, dim);
/* Check for empty array */
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2833,2841 ****
nulls = (bool *) palloc(nitems * sizeof(bool));
/* Loop over source data */
! s = ARR_DATA_PTR(v);
! bitmap = ARR_NULLBITMAP(v);
! bitmask = 1;
hasnulls = false;
for (i = 0; i < nitems; i++)
--- 3165,3171 ----
nulls = (bool *) palloc(nitems * sizeof(bool));
/* Loop over source data */
! ARRAY_ITER_SETUP(iter, v);
hasnulls = false;
for (i = 0; i < nitems; i++)
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2843,2860 ****
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;
! }
/*
* Apply the given function to source elt and extra args.
--- 3173,3180 ----
bool callit = true;
/* Get source element, checking for NULL */
! ARRAY_ITER_NEXT(iter, i, fcinfo->arg[0], fcinfo->argnull[0],
! inp_typlen, inp_typbyval, inp_typalign);
/*
* Apply the given function to source elt and extra args.
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2899,2915 ****
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 */
--- 3219,3224 ----
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2928,2934 ****
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = retType;
! memcpy(ARR_DIMS(result), ARR_DIMS(v), 2 * ndim * sizeof(int));
/*
* Note: do not risk trying to pfree the results of the called function
--- 3237,3244 ----
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = retType;
! 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
*************** construct_empty_array(Oid elmtype)
*** 3092,3097 ****
--- 3402,3424 ----
}
/*
+ * 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
*
* array: array object to examine (must not be NULL)
*************** array_contains_nulls(ArrayType *array)
*** 3229,3264 ****
Datum
array_eq(PG_FUNCTION_ARGS)
{
! ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
! ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(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);
bool result = true;
int nitems;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
! char *ptr1;
! char *ptr2;
! bits8 *bitmap1;
! bits8 *bitmap2;
! int bitmask;
int i;
FunctionCallInfoData locfcinfo;
! if (element_type != ARR_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)
result = false;
else
{
--- 3556,3591 ----
Datum
array_eq(PG_FUNCTION_ARGS)
{
! AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
! AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
! 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;
! ARRAY_ITER ARRAY_ITER_VARS(it1);
! ARRAY_ITER ARRAY_ITER_VARS(it2);
int i;
FunctionCallInfoData locfcinfo;
! 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, ndims1 * sizeof(int)) != 0 ||
! memcmp(lbs1, lbs2, ndims1 * sizeof(int)) != 0)
result = false;
else
{
*************** array_eq(PG_FUNCTION_ARGS)
*** 3293,3303 ****
/* 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 */
for (i = 0; i < nitems; i++)
{
--- 3620,3627 ----
/* Loop over source data */
nitems = ArrayGetNItems(ndims1, dims1);
! ARRAY_ITER_SETUP(it1, array1);
! ARRAY_ITER_SETUP(it2, array2);
for (i = 0; i < nitems; i++)
{
*************** array_eq(PG_FUNCTION_ARGS)
*** 3308,3349 ****
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;
! }
/*
* We consider two NULLs equal; NULL and not-NULL are unequal.
--- 3632,3639 ----
bool oprresult;
/* Get elements, checking for NULL */
! ARRAY_ITER_NEXT(it1, i, elt1, isnull1, typlen, typbyval, typalign);
! ARRAY_ITER_NEXT(it2, i, elt2, isnull2, typlen, typbyval, typalign);
/*
* We consider two NULLs equal; NULL and not-NULL are unequal.
*************** array_eq(PG_FUNCTION_ARGS)
*** 3374,3381 ****
}
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array1, 0);
! PG_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
--- 3664,3671 ----
}
/* Avoid leaking memory when handed toasted input. */
! AARR_FREE_IF_COPY(array1, 0);
! AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
*************** btarraycmp(PG_FUNCTION_ARGS)
*** 3435,3465 ****
static int
array_cmp(FunctionCallInfo fcinfo)
{
! ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
! ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(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 nitems1 = ArrayGetNItems(ndims1, dims1);
int nitems2 = ArrayGetNItems(ndims2, dims2);
! Oid element_type = ARR_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;
int i;
FunctionCallInfoData locfcinfo;
! if (element_type != ARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
--- 3725,3752 ----
static int
array_cmp(FunctionCallInfo fcinfo)
{
! AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
! AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
! 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 = AARR_ELEMTYPE(array1);
int result = 0;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
int min_nitems;
! ARRAY_ITER ARRAY_ITER_VARS(it1);
! ARRAY_ITER ARRAY_ITER_VARS(it2);
int i;
FunctionCallInfoData locfcinfo;
! if (element_type != AARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
*************** array_cmp(FunctionCallInfo fcinfo)
*** 3495,3505 ****
/* 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 */
for (i = 0; i < min_nitems; i++)
{
--- 3782,3789 ----
/* Loop over source data */
min_nitems = Min(nitems1, nitems2);
! ARRAY_ITER_SETUP(it1, array1);
! ARRAY_ITER_SETUP(it2, array2);
for (i = 0; i < min_nitems; i++)
{
*************** array_cmp(FunctionCallInfo fcinfo)
*** 3510,3551 ****
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;
! }
/*
* We consider two NULLs equal; NULL > not-NULL.
--- 3794,3801 ----
int32 cmpresult;
/* Get elements, checking for NULL */
! ARRAY_ITER_NEXT(it1, i, elt1, isnull1, typlen, typbyval, typalign);
! ARRAY_ITER_NEXT(it2, i, elt2, isnull2, typlen, typbyval, typalign);
/*
* We consider two NULLs equal; NULL > not-NULL.
*************** array_cmp(FunctionCallInfo fcinfo)
*** 3604,3611 ****
result = (ndims1 < ndims2) ? -1 : 1;
else
{
! /* this relies on LB array immediately following DIMS array */
! for (i = 0; i < ndims1 * 2; i++)
{
if (dims1[i] != dims2[i])
{
--- 3854,3860 ----
result = (ndims1 < ndims2) ? -1 : 1;
else
{
! for (i = 0; i < ndims1; i++)
{
if (dims1[i] != dims2[i])
{
*************** array_cmp(FunctionCallInfo fcinfo)
*** 3613,3624 ****
break;
}
}
}
}
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array1, 0);
! PG_FREE_IF_COPY(array2, 1);
return result;
}
--- 3862,3887 ----
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. */
! AARR_FREE_IF_COPY(array1, 0);
! AARR_FREE_IF_COPY(array2, 1);
return result;
}
*************** array_cmp(FunctionCallInfo fcinfo)
*** 3633,3652 ****
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);
uint32 result = 1;
int nitems;
TypeCacheEntry *typentry;
int typlen;
bool typbyval;
char typalign;
- char *ptr;
- bits8 *bitmap;
- int bitmask;
int i;
FunctionCallInfoData locfcinfo;
/*
--- 3896,3913 ----
Datum
hash_array(PG_FUNCTION_ARGS)
{
! 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;
int i;
+ ARRAY_ITER ARRAY_ITER_VARS(iter);
FunctionCallInfoData locfcinfo;
/*
*************** hash_array(PG_FUNCTION_ARGS)
*** 3680,3707 ****
/* Loop over source data */
nitems = ArrayGetNItems(ndims, dims);
! ptr = ARR_DATA_PTR(array);
! bitmap = ARR_NULLBITMAP(array);
! bitmask = 1;
for (i = 0; i < nitems; i++)
{
uint32 elthash;
/* Get element, checking for NULL */
! if (bitmap && (*bitmap & bitmask) == 0)
{
/* 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;
--- 3941,3964 ----
/* Loop over source data */
nitems = ArrayGetNItems(ndims, dims);
! ARRAY_ITER_SETUP(iter, array);
for (i = 0; i < nitems; i++)
{
+ Datum elt;
+ bool isnull;
uint32 elthash;
/* Get element, checking for NULL */
! ARRAY_ITER_NEXT(iter, i, elt, isnull, typlen, typbyval, typalign);
!
! if (isnull)
{
/* Treat nulls as having hashvalue 0 */
elthash = 0;
}
else
{
/* Apply the hash function */
locfcinfo.arg[0] = elt;
locfcinfo.argnull[0] = false;
*************** hash_array(PG_FUNCTION_ARGS)
*** 3709,3725 ****
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.
--- 3966,3971 ----
*************** hash_array(PG_FUNCTION_ARGS)
*** 3735,3741 ****
}
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array, 0);
PG_RETURN_UINT32(result);
}
--- 3981,3987 ----
}
/* Avoid leaking memory when handed toasted input. */
! AARR_FREE_IF_COPY(array, 0);
PG_RETURN_UINT32(result);
}
*************** hash_array(PG_FUNCTION_ARGS)
*** 3756,3766 ****
* 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,
bool matchall, void **fn_extra)
{
bool result = matchall;
! Oid element_type = ARR_ELEMTYPE(array1);
TypeCacheEntry *typentry;
int nelems1;
Datum *values2;
--- 4002,4012 ----
* When matchall is false, return true if any members of array1 are in array2.
*/
static bool
! array_contain_compare(AnyArrayType *array1, AnyArrayType *array2, Oid collation,
bool matchall, void **fn_extra)
{
bool result = matchall;
! Oid element_type = AARR_ELEMTYPE(array1);
TypeCacheEntry *typentry;
int nelems1;
Datum *values2;
*************** array_contain_compare(ArrayType *array1,
*** 3769,3782 ****
int typlen;
bool typbyval;
char typalign;
- char *ptr1;
- bits8 *bitmap1;
- int bitmask;
int i;
int j;
FunctionCallInfoData locfcinfo;
! if (element_type != ARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
--- 4015,4026 ----
int typlen;
bool typbyval;
char typalign;
int i;
int j;
+ ARRAY_ITER ARRAY_ITER_VARS(it1);
FunctionCallInfoData locfcinfo;
! if (element_type != AARR_ELEMTYPE(array2))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot compare arrays of different element types")));
*************** array_contain_compare(ArrayType *array1,
*** 3809,3816 ****
* 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);
/*
* Apply the comparison operator to each pair of array elements.
--- 4053,4070 ----
* 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.
*/
! 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.
*************** array_contain_compare(ArrayType *array1,
*** 3819,3828 ****
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;
for (i = 0; i < nelems1; i++)
{
--- 4073,4080 ----
collation, NULL, NULL);
/* Loop over source data */
! nelems1 = ArrayGetNItems(AARR_NDIM(array1), AARR_DIMS(array1));
! ARRAY_ITER_SETUP(it1, array1);
for (i = 0; i < nelems1; i++)
{
*************** array_contain_compare(ArrayType *array1,
*** 3830,3856 ****
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;
! }
/*
* We assume that the comparison operator is strict, so a NULL can't
--- 4082,4088 ----
bool isnull1;
/* Get element, checking for NULL */
! ARRAY_ITER_NEXT(it1, i, elt1, isnull1, typlen, typbyval, typalign);
/*
* We assume that the comparison operator is strict, so a NULL can't
*************** array_contain_compare(ArrayType *array1,
*** 3909,3925 ****
}
}
- 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);
Oid collation = PG_GET_COLLATION();
bool result;
--- 4141,4154 ----
}
}
return result;
}
Datum
arrayoverlap(PG_FUNCTION_ARGS)
{
! AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
! AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
*************** arrayoverlap(PG_FUNCTION_ARGS)
*** 3927,3934 ****
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array1, 0);
! PG_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
--- 4156,4163 ----
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! AARR_FREE_IF_COPY(array1, 0);
! AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
*************** arrayoverlap(PG_FUNCTION_ARGS)
*** 3936,3943 ****
Datum
arraycontains(PG_FUNCTION_ARGS)
{
! ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
! ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
Oid collation = PG_GET_COLLATION();
bool result;
--- 4165,4172 ----
Datum
arraycontains(PG_FUNCTION_ARGS)
{
! AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
! AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
*************** arraycontains(PG_FUNCTION_ARGS)
*** 3945,3952 ****
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array1, 0);
! PG_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
--- 4174,4181 ----
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! AARR_FREE_IF_COPY(array1, 0);
! AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
*************** arraycontains(PG_FUNCTION_ARGS)
*** 3954,3961 ****
Datum
arraycontained(PG_FUNCTION_ARGS)
{
! ArrayType *array1 = PG_GETARG_ARRAYTYPE_P(0);
! ArrayType *array2 = PG_GETARG_ARRAYTYPE_P(1);
Oid collation = PG_GET_COLLATION();
bool result;
--- 4183,4190 ----
Datum
arraycontained(PG_FUNCTION_ARGS)
{
! AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
! AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
*************** arraycontained(PG_FUNCTION_ARGS)
*** 3963,3970 ****
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! PG_FREE_IF_COPY(array1, 0);
! PG_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
--- 4192,4199 ----
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
! AARR_FREE_IF_COPY(array1, 0);
! AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
*************** initArrayResult(Oid element_type, Memory
*** 4702,4708 ****
MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
astate->mcontext = arr_context;
astate->private_cxt = subcontext;
! astate->alen = (subcontext ? 64 : 8); /* arbitrary starting array size */
astate->dvalues = (Datum *)
MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
astate->dnulls = (bool *)
--- 4931,4938 ----
MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
astate->mcontext = arr_context;
astate->private_cxt = subcontext;
! astate->alen = (subcontext ? 64 : 8); /* arbitrary starting array
! * size */
astate->dvalues = (Datum *)
MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
astate->dnulls = (bool *)
*************** initArrayResultArr(Oid array_type, Oid e
*** 4878,4887 ****
bool subcontext)
{
ArrayBuildStateArr *astate;
! MemoryContext arr_context = rcontext; /* by default use the parent ctx */
/* Lookup element type, unless element_type already provided */
! if (! OidIsValid(element_type))
{
element_type = get_element_type(array_type);
--- 5108,5118 ----
bool subcontext)
{
ArrayBuildStateArr *astate;
! MemoryContext arr_context = rcontext; /* by default use the parent
! * ctx */
/* Lookup element type, unless element_type already provided */
! if (!OidIsValid(element_type))
{
element_type = get_element_type(array_type);
*************** makeArrayResultAny(ArrayBuildStateAny *a
*** 5259,5289 ****
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);
}
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);
}
--- 5490,5508 ----
Datum
array_larger(PG_FUNCTION_ARGS)
{
! 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)
{
! if (array_cmp(fcinfo) < 0)
! PG_RETURN_DATUM(PG_GETARG_DATUM(0));
! else
! PG_RETURN_DATUM(PG_GETARG_DATUM(1));
}
*************** generate_subscripts(PG_FUNCTION_ARGS)
*** 5308,5314 ****
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
! ArrayType *v = PG_GETARG_ARRAYTYPE_P(0);
int reqdim = PG_GETARG_INT32(1);
int *lb,
*dimv;
--- 5527,5533 ----
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
! AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
int reqdim = PG_GETARG_INT32(1);
int *lb,
*dimv;
*************** generate_subscripts(PG_FUNCTION_ARGS)
*** 5317,5327 ****
funcctx = SRF_FIRSTCALL_INIT();
/* Sanity check: does it look like an array at all? */
! if (ARR_NDIM(v) <= 0 || ARR_NDIM(v) > MAXDIM)
SRF_RETURN_DONE(funcctx);
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > ARR_NDIM(v))
SRF_RETURN_DONE(funcctx);
/*
--- 5536,5546 ----
funcctx = SRF_FIRSTCALL_INIT();
/* Sanity check: does it look like an array at all? */
! if (AARR_NDIM(v) <= 0 || AARR_NDIM(v) > MAXDIM)
SRF_RETURN_DONE(funcctx);
/* Sanity check: was the requested dim valid */
! if (reqdim <= 0 || reqdim > AARR_NDIM(v))
SRF_RETURN_DONE(funcctx);
/*
*************** generate_subscripts(PG_FUNCTION_ARGS)
*** 5330,5337 ****
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = (generate_subscripts_fctx *) palloc(sizeof(generate_subscripts_fctx));
! lb = ARR_LBOUND(v);
! dimv = ARR_DIMS(v);
fctx->lower = lb[reqdim - 1];
fctx->upper = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
--- 5549,5556 ----
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = (generate_subscripts_fctx *) palloc(sizeof(generate_subscripts_fctx));
! lb = AARR_LBOUND(v);
! dimv = AARR_DIMS(v);
fctx->lower = lb[reqdim - 1];
fctx->upper = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
*************** array_unnest(PG_FUNCTION_ARGS)
*** 5650,5660 ****
{
typedef struct
{
! ArrayType *arr;
int nextelem;
int numelems;
- char *elemdataptr; /* this moves with nextelem */
- bits8 *arraynullsptr; /* this does not */
int16 elmlen;
bool elmbyval;
char elmalign;
--- 5869,5877 ----
{
typedef struct
{
! ARRAY_ITER ARRAY_ITER_VARS(iter);
int nextelem;
int numelems;
int16 elmlen;
bool elmbyval;
char elmalign;
*************** array_unnest(PG_FUNCTION_ARGS)
*** 5667,5673 ****
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
! ArrayType *arr;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
--- 5884,5890 ----
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
! AnyArrayType *arr;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
*************** array_unnest(PG_FUNCTION_ARGS)
*** 5684,5706 ****
* and not before. (If no detoast happens, we assume the originally
* passed array will stick around till then.)
*/
! arr = PG_GETARG_ARRAYTYPE_P(0);
/* allocate memory for user context */
fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx));
/* initialize state */
! fctx->arr = arr;
fctx->nextelem = 0;
! fctx->numelems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
!
! fctx->elemdataptr = ARR_DATA_PTR(arr);
! fctx->arraynullsptr = ARR_NULLBITMAP(arr);
! get_typlenbyvalalign(ARR_ELEMTYPE(arr),
! &fctx->elmlen,
! &fctx->elmbyval,
! &fctx->elmalign);
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
--- 5901,5928 ----
* and not before. (If no detoast happens, we assume the originally
* passed array will stick around till then.)
*/
! arr = PG_GETARG_ANY_ARRAY(0);
/* allocate memory for user context */
fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx));
/* initialize state */
! ARRAY_ITER_SETUP(fctx->iter, arr);
fctx->nextelem = 0;
! fctx->numelems = ArrayGetNItems(AARR_NDIM(arr), AARR_DIMS(arr));
! 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);
*************** array_unnest(PG_FUNCTION_ARGS)
*** 5715,5746 ****
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;
! }
SRF_RETURN_NEXT(funcctx, elem);
}
--- 5937,5944 ----
int offset = fctx->nextelem++;
Datum elem;
! ARRAY_ITER_NEXT(fctx->iter, offset, elem, fcinfo->isnull,
! fctx->elmlen, fctx->elmbyval, fctx->elmalign);
SRF_RETURN_NEXT(funcctx, elem);
}
*************** array_replace_internal(ArrayType *array,
*** 5992,5998 ****
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = element_type;
! memcpy(ARR_DIMS(result), ARR_DIMS(array), 2 * ndim * sizeof(int));
if (remove)
{
--- 6190,6197 ----
result->ndim = ndim;
result->dataoffset = dataoffset;
result->elemtype = element_type;
! memcpy(ARR_DIMS(result), ARR_DIMS(array), ndim * sizeof(int));
! memcpy(ARR_LBOUND(result), ARR_LBOUND(array), ndim * sizeof(int));
if (remove)
{
diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c
index 014eca5..e8af030 100644
*** a/src/backend/utils/adt/datum.c
--- b/src/backend/utils/adt/datum.c
***************
*** 12,19 ****
*
*-------------------------------------------------------------------------
*/
/*
! * In the implementation of the next 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
--- 12,20 ----
*
*-------------------------------------------------------------------------
*/
+
/*
! * 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,44 ****
--- 35,49 ----
*
* 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,51 ****
--- 51,57 ----
*
* 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.
*************** datumGetSize(Datum value, bool typByVal,
*** 106,114 ****
/*-------------------------------------------------------------------------
* datumCopy
*
! * make a copy of a datum
*
* If the datatype is pass-by-reference, memory is obtained with palloc().
*-------------------------------------------------------------------------
*/
Datum
--- 112,127 ----
/*-------------------------------------------------------------------------
* datumCopy
*
! * 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
*************** datumCopy(Datum value, bool typByVal, in
*** 118,161 ****
if (typByVal)
res = value;
else
{
Size realSize;
! char *s;
!
! if (DatumGetPointer(value) == NULL)
! return PointerGetDatum(NULL);
realSize = datumGetSize(value, typByVal, typLen);
! s = (char *) palloc(realSize);
! memcpy(s, DatumGetPointer(value), realSize);
! res = PointerGetDatum(s);
}
return res;
}
/*-------------------------------------------------------------------------
! * datumFree
*
! * Free the space occupied by a datum CREATED BY "datumCopy"
*
! * NOTE: DO NOT USE THIS ROUTINE with datums returned by heap_getattr() etc.
! * ONLY datums created by "datumCopy" can be freed!
*-------------------------------------------------------------------------
*/
! #ifdef NOT_USED
! void
! datumFree(Datum value, bool typByVal, int typLen)
{
! if (!typByVal)
! {
! Pointer s = DatumGetPointer(value);
!
! pfree(s);
! }
}
- #endif
/*-------------------------------------------------------------------------
* datumIsEqual
--- 131,201 ----
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 *resultptr;
realSize = datumGetSize(value, typByVal, typLen);
! resultptr = (char *) palloc(realSize);
! memcpy(resultptr, DatumGetPointer(value), realSize);
! res = PointerGetDatum(resultptr);
}
return res;
}
/*-------------------------------------------------------------------------
! * datumTransfer
*
! * Transfer a non-NULL datum into the current memory context.
*
! * 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).
*-------------------------------------------------------------------------
*/
! Datum
! datumTransfer(Datum value, bool typByVal, int typLen)
{
! if (!typByVal && typLen == -1 &&
! VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(value)))
! value = TransferExpandedObject(value, CurrentMemoryContext);
! else
! value = datumCopy(value, typByVal, typLen);
! return value;
}
/*-------------------------------------------------------------------------
* datumIsEqual
diff --git a/src/backend/utils/adt/expandeddatum.c b/src/backend/utils/adt/expandeddatum.c
index ...039671b .
*** a/src/backend/utils/adt/expandeddatum.c
--- b/src/backend/utils/adt/expandeddatum.c
***************
*** 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);
+ }
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index c42a6b6..34f4e72 100644
*** a/src/backend/utils/mmgr/mcxt.c
--- b/src/backend/utils/mmgr/mcxt.c
*************** MemoryContextSetParent(MemoryContext con
*** 323,328 ****
--- 323,332 ----
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)
{
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 9e912ba..fbcae0c 100644
*** a/src/include/executor/spi.h
--- b/src/include/executor/spi.h
*************** extern char *SPI_getnspname(Relation rel
*** 124,129 ****
--- 124,130 ----
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);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 48f84bf..00686b0 100644
*** a/src/include/executor/tuptable.h
--- b/src/include/executor/tuptable.h
*************** extern Datum ExecFetchSlotTupleDatum(Tup
*** 163,168 ****
--- 163,169 ----
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);
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 4f1d234..deaa3c5 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct WindowFunc
*** 305,310 ****
--- 305,314 ----
* 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
diff --git a/src/include/postgres.h b/src/include/postgres.h
index be37313..ccf1605 100644
*** a/src/include/postgres.h
--- b/src/include/postgres.h
*************** typedef struct varatt_indirect
*** 88,93 ****
--- 88,110 ----
} 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
* with a previous notion that the tag field was the pointer datum's length.
*************** typedef struct varatt_indirect
*** 95,105 ****
--- 112,129 ----
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"))
*************** typedef struct
*** 294,299 ****
--- 318,329 ----
(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))
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 0a488e7..f76443d 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
***************
*** 45,50 ****
--- 45,55 ----
* 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,62 ****
--- 62,69 ----
#define ARRAY_H
#include "fmgr.h"
+ #include "utils/expandeddatum.h"
+
/*
* Arrays are varlena objects, so must meet the varlena convention that
*************** typedef struct
*** 75,80 ****
--- 82,167 ----
} 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)
*/
*************** typedef struct ArrayMapState
*** 151,167 ****
/* ArrayIteratorData is private in arrayfuncs.c */
typedef struct ArrayIteratorData *ArrayIterator;
! /*
! * fmgr macros for 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)
/*
! * Access macros for array header fields.
*
* ARR_DIMS returns a pointer to an array of array dimensions (number of
* elements along the various array axes).
--- 238,261 ----
/* ArrayIteratorData is private in arrayfuncs.c */
typedef struct ArrayIteratorData *ArrayIterator;
! /* 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 varlena array header fields.
*
* ARR_DIMS returns a pointer to an array of array dimensions (number of
* elements along the various array axes).
*************** typedef struct ArrayIteratorData *ArrayI
*** 209,214 ****
--- 303,404 ----
#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))
+
+ /*
+ * Macros for iterating through elements of a flat or expanded array.
+ * Use "ARRAY_ITER ARRAY_ITER_VARS(name);" to declare the local variables
+ * needed for an iterator (more than one set can be used in the same function,
+ * if they have different names).
+ * Use "ARRAY_ITER_SETUP(name, arrayptr);" to prepare to iterate, and
+ * "ARRAY_ITER_NEXT(name, index, datumvar, isnullvar, ...);" 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.
+ */
+ #define ARRAY_ITER /* dummy type name to keep pgindent happy */
+
+ #define ARRAY_ITER_VARS(iter) \
+ Datum *iter##datumptr; \
+ bool *iter##isnullptr; \
+ char *iter##dataptr; \
+ bits8 *iter##bitmapptr; \
+ int iter##bitmask
+
+ #define ARRAY_ITER_SETUP(iter, arrayptr) \
+ do { \
+ if (VARATT_IS_EXPANDED_HEADER(arrayptr)) \
+ { \
+ if ((arrayptr)->xpn.dvalues) \
+ { \
+ (iter##datumptr) = (arrayptr)->xpn.dvalues; \
+ (iter##isnullptr) = (arrayptr)->xpn.dnulls; \
+ (iter##dataptr) = NULL; \
+ (iter##bitmapptr) = NULL; \
+ } \
+ else \
+ { \
+ (iter##datumptr) = NULL; \
+ (iter##isnullptr) = NULL; \
+ (iter##dataptr) = ARR_DATA_PTR((arrayptr)->xpn.fvalue); \
+ (iter##bitmapptr) = ARR_NULLBITMAP((arrayptr)->xpn.fvalue); \
+ } \
+ } \
+ else \
+ { \
+ (iter##datumptr) = NULL; \
+ (iter##isnullptr) = NULL; \
+ (iter##dataptr) = ARR_DATA_PTR(&(arrayptr)->flt); \
+ (iter##bitmapptr) = ARR_NULLBITMAP(&(arrayptr)->flt); \
+ } \
+ (iter##bitmask) = 1; \
+ } while (0)
+
+ #define ARRAY_ITER_NEXT(iter,i, datumvar,isnullvar, elmlen,elmbyval,elmalign) \
+ do { \
+ if (iter##datumptr) \
+ { \
+ (datumvar) = (iter##datumptr)[i]; \
+ (isnullvar) = (iter##isnullptr) ? (iter##isnullptr)[i] : false; \
+ } \
+ else \
+ { \
+ if ((iter##bitmapptr) && (*(iter##bitmapptr) & (iter##bitmask)) == 0) \
+ { \
+ (isnullvar) = true; \
+ (datumvar) = (Datum) 0; \
+ } \
+ else \
+ { \
+ (isnullvar) = false; \
+ (datumvar) = fetch_att(iter##dataptr, elmbyval, elmlen); \
+ (iter##dataptr) = att_addlength_pointer(iter##dataptr, elmlen, iter##dataptr); \
+ (iter##dataptr) = (char *) att_align_nominal(iter##dataptr, elmalign); \
+ } \
+ (iter##bitmask) <<= 1; \
+ if ((iter##bitmask) == 0x100) \
+ { \
+ if (iter##bitmapptr) \
+ (iter##bitmapptr)++; \
+ (iter##bitmask) = 1; \
+ } \
+ } \
+ } while (0)
+
/*
* GUC parameter
*************** extern Datum array_remove(PG_FUNCTION_AR
*** 250,255 ****
--- 440,454 ----
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);
*************** extern ArrayType *array_set(ArrayType *a
*** 271,277 ****
Datum dataValue, bool isNull,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);
! extern Datum array_map(FunctionCallInfo fcinfo, Oid inpType, Oid retType,
ArrayMapState *amstate);
extern void array_bitmap_copy(bits8 *destbitmap, int destoffset,
--- 470,476 ----
Datum dataValue, bool isNull,
int arraytyplen, int elmlen, bool elmbyval, char elmalign);
! extern Datum array_map(FunctionCallInfo fcinfo, Oid retType,
ArrayMapState *amstate);
extern void array_bitmap_copy(bits8 *destbitmap, int destoffset,
*************** extern ArrayType *construct_md_array(Dat
*** 288,293 ****
--- 487,495 ----
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 *cu
*** 341,346 ****
--- 543,559 ----
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
*/
extern Datum array_append(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/datum.h b/src/include/utils/datum.h
index 663414b..c572f79 100644
*** a/src/include/utils/datum.h
--- b/src/include/utils/datum.h
***************
*** 24,41 ****
extern Size datumGetSize(Datum value, bool typByVal, int typLen);
/*
! * datumCopy - make a copy of a 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.
*
! * Does nothing if datatype is pass-by-value.
*/
! extern void datumFree(Datum value, bool typByVal, int typLen);
/*
* datumIsEqual
--- 24,41 ----
extern Size datumGetSize(Datum value, bool typByVal, int typLen);
/*
! * 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);
/*
! * datumTransfer - transfer a non-NULL datum into the current memory context.
*
! * Differs from datumCopy() in its handling of read-write expanded objects.
*/
! extern Datum datumTransfer(Datum value, bool typByVal, int typLen);
/*
* datumIsEqual
diff --git a/src/include/utils/expandeddatum.h b/src/include/utils/expandeddatum.h
index ...3a8336e .
*** a/src/include/utils/expandeddatum.h
--- b/src/include/utils/expandeddatum.h
***************
*** 0 ****
--- 1,148 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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 (which
+ * must be a valid in-line, non-compressed, 4-byte-header varlena object).
+ *
+ * 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).
+ *
+ * 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 */
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 650cc48..0ff2086 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** build_datatype(HeapTuple typeTup, int32
*** 2200,2205 ****
--- 2200,2221 ----
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;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index deefb1f..aac7cda 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 34,39 ****
--- 34,40 ----
#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 void exec_prepare_plan(PLpgSQL_ex
*** 173,178 ****
--- 174,181 ----
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,
*************** plpgsql_exec_function(PLpgSQL_function *
*** 312,317 ****
--- 315,358 ----
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;
*************** plpgsql_exec_function(PLpgSQL_function *
*** 477,494 ****
/*
* If the function's return type isn't by value, copy the value
! * into upper executor memory 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);
! }
}
}
--- 518,531 ----
/*
* If the function's return type isn't by value, copy the value
! * 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)
! estate.retval = SPI_datumTransfer(estate.retval,
! false,
! func->fn_rettyplen);
}
}
*************** exec_stmt_return(PLpgSQL_execstate *esta
*** 2476,2481 ****
--- 2513,2525 ----
* 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)
{
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2604,2609 ****
--- 2648,2658 ----
* 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)
{
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2622,2627 ****
--- 2671,2681 ----
(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,
*************** exec_prepare_plan(PLpgSQL_execstate *est
*** 3333,3338 ****
--- 3387,3399 ----
/* 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;
}
*************** exec_assign_expr(PLpgSQL_execstate *esta
*** 4071,4076 ****
--- 4132,4150 ----
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);
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4140,4165 ****
/*
* If type is by-reference, copy the new value (which is
* probably in the eval_econtext) into the procedure's memory
! * context.
*/
if (!var->datatype->typbyval && !isNull)
! newvalue = datumCopy(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.)
*/
! free_var(var);
var->value = newvalue;
var->isnull = isNull;
! if (!var->datatype->typbyval && !isNull)
! var->freeval = true;
break;
}
--- 4214,4264 ----
/*
* If type is by-reference, copy the new value (which is
* probably in the eval_econtext) into the procedure's memory
! * 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)
! {
! 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, 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.
*/
! if (var->value != newvalue || var->isnull || isNull)
! free_var(var);
var->value = newvalue;
var->isnull = isNull;
! var->freeval = (!var->datatype->typbyval && !isNull);
break;
}
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4505,4514 ****
*
* 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.
*/
static void
exec_eval_datum(PLpgSQL_execstate *estate,
--- 4604,4617 ----
*
* At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
*
! * 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,
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5216,5221 ****
--- 5319,5327 ----
{
/* 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 */
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5362,5368 ****
*/
MemSet(paramLI->params, 0, estate->ndatums * sizeof(ParamExternData));
! /* Instantiate values for "safe" parameters of the expression */
dno = -1;
while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
{
--- 5468,5480 ----
*/
MemSet(paramLI->params, 0, estate->ndatums * sizeof(ParamExternData));
! /*
! * 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)
{
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5373,5379 ****
PLpgSQL_var *var = (PLpgSQL_var *) datum;
ParamExternData *prm = ¶mLI->params[dno];
! prm->value = var->value;
prm->isnull = var->isnull;
prm->pflags = PARAM_FLAG_CONST;
prm->ptype = var->datatype->typoid;
--- 5485,5496 ----
PLpgSQL_var *var = (PLpgSQL_var *) datum;
ParamExternData *prm = ¶mLI->params[dno];
! 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;
*************** plpgsql_param_fetch(ParamListInfo params
*** 5442,5447 ****
--- 5559,5573 ----
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);
}
*************** exec_simple_recheck_plan(PLpgSQL_expr *e
*** 6384,6389 ****
--- 6510,6622 ----
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
* ----------
*************** free_var(PLpgSQL_var *var)
*** 6540,6546 ****
{
if (var->freeval)
{
! pfree(DatumGetPointer(var->value));
var->freeval = false;
}
}
--- 6773,6784 ----
{
if (var->freeval)
{
! if (DatumIsReadWriteExpandedObject(var->value,
! var->isnull,
! var->datatype->typlen))
! DeleteExpandedObject(var->value);
! else
! pfree(DatumGetPointer(var->value));
var->freeval = false;
}
}
*************** format_expr_params(PLpgSQL_execstate *es
*** 6750,6757 ****
curvar = (PLpgSQL_var *) estate->datums[dno];
! exec_eval_datum(estate, (PLpgSQL_datum *) curvar, ¶mtypeid,
! ¶mtypmod, ¶mdatum, ¶misnull);
appendStringInfo(¶mstr, "%s%s = ",
paramno > 0 ? ", " : "",
--- 6988,6996 ----
curvar = (PLpgSQL_var *) estate->datums[dno];
! exec_eval_datum(estate, (PLpgSQL_datum *) curvar,
! ¶mtypeid, ¶mtypmod,
! ¶mdatum, ¶misnull);
appendStringInfo(¶mstr, "%s%s = ",
paramno > 0 ? ", " : "",
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 4026e41..0097890 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** read_sql_construct(int until,
*** 2625,2630 ****
--- 2625,2631 ----
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
*************** make_execsql_stmt(int firsttoken, int lo
*** 2849,2854 ****
--- 2850,2856 ----
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
*************** read_cursor_args(PLpgSQL_var *cursor, in
*** 3732,3737 ****
--- 3734,3740 ----
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index bec773a..93c2504 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct
*** 183,188 ****
--- 183,189 ----
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;
*************** typedef struct PLpgSQL_expr
*** 216,221 ****
--- 217,223 ----
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;
pgsql-hackers by date: