Re: Manipulating complex types as non-contiguous structures in-memory

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Manipulating complex types as non-contiguous structures in-memory
Дата
Msg-id 29085.1430866213@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: Manipulating complex types as non-contiguous structures in-memory  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: Manipulating complex types as non-contiguous structures in-memory  (Pavel Stehule <pavel.stehule@gmail.com>)
Re: Manipulating complex types as non-contiguous structures in-memory  (Andres Freund <andres@anarazel.de>)
Список 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 по дате отправления:

Предыдущее
От: Peter Geoghegan
Дата:
Сообщение: Re: INSERT ... ON CONFLICT UPDATE/IGNORE 4.0
Следующее
От: Andreas Karlsson
Дата:
Сообщение: Re: BRIN range operator class