diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 867035d..860ad78 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 f8c1401..ebcbbc4 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/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 4b86e91..daa9f69 100644
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
*************** SPI_pfree(void *pointer)
*** 1014,1019 ****
--- 1014,1040 ----
  	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 20e5ff1..d1ed33f 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 ...4879a75 .
*** a/src/backend/utils/adt/array_expanded.c
--- b/src/backend/utils/adt/array_expanded.c
***************
*** 0 ****
--- 1,1101 ----
+ /*-------------------------------------------------------------------------
+  *
+  * array_expanded.c
+  *	  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/builtins.h"
+ #include "utils/datum.h"
+ #include "utils/expandeddatum.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ 
+ 
+ /*
+  * 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;
+ 
+ /* "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
+ };
+ 
+ /*
+  * 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;
+ 
+ /*
+  * 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))
+ 
+ 
+ /*
+  * expand_array: convert an array Datum into an expanded array
+  *
+  * The expanded object will be a child of parentcontext.
+  *
+  * Caller can provide element type's representational data; we do that because
+  * caller is often in a position to cache it across repeated calls.  If the
+  * caller can't do that, pass zeroes for elmlen/elmbyval/elmalign.
+  */
+ Datum
+ expand_array(Datum arraydatum, MemoryContext parentcontext,
+ 			 int elmlen, bool elmbyval, char elmalign)
+ {
+ 	ArrayType  *array;
+ 	ExpandedArrayHeader *eah;
+ 	MemoryContext objcxt;
+ 	MemoryContext oldcxt;
+ 
+ 	/* allocate private context for expanded object */
+ 	/* TODO: should we use some other memory context size parameters? */
+ 	objcxt = AllocSetContextCreate(parentcontext,
+ 								   "expanded array",
+ 								   ALLOCSET_DEFAULT_MINSIZE,
+ 								   ALLOCSET_DEFAULT_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;
+ 
+ 	/*
+ 	 * Detoast and copy original array into private context, as a flat array.
+ 	 * We flatten it even if it's in expanded form; it's not clear that adding
+ 	 * a special-case path for that would be worth the trouble.
+ 	 *
+ 	 * 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 (elmlen)
+ 	{
+ 		/* Caller provided representational data */
+ 		eah->typlen = elmlen;
+ 		eah->typbyval = elmbyval;
+ 		eah->typalign = elmalign;
+ 	}
+ 	else
+ 	{
+ 		/* No, so look it up */
+ 		get_typlenbyvalalign(eah->element_type,
+ 							 &eah->typlen,
+ 							 &eah->typbyval,
+ 							 &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 PointerGetDatum(eah->hdr.eoh_rw_ptr);
+ }
+ 
+ /*
+  * construct_empty_expanded_array: make an empty expanded array
+  * given only type information.  (elmlen etc can be zeroes.)
+  */
+ static ExpandedArrayHeader *
+ construct_empty_expanded_array(Oid element_type,
+ 							   MemoryContext parentcontext,
+ 							   int elmlen, bool elmbyval, char elmalign)
+ {
+ 	ArrayType  *array = construct_empty_array(element_type);
+ 	Datum		d;
+ 
+ 	d = expand_array(PointerGetDatum(array), parentcontext,
+ 					 elmlen, elmbyval, elmalign);
+ 	return (ExpandedArrayHeader *) DatumGetEOHP(d);
+ }
+ 
+ 
+ /*
+  * 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
+  */
+ 
+ #ifdef NOT_YET_USED
+ 
+ /*
+  * DatumGetExpandedArray: get a writable expanded array from an input argument
+  */
+ static ExpandedArrayHeader *
+ DatumGetExpandedArray(Datum d)
+ {
+ 	ExpandedArrayHeader *eah;
+ 
+ 	/* If it's a writable expanded array already, just return it */
+ 	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
+ 	{
+ 		eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
+ 		Assert(eah->ea_magic == EA_MAGIC);
+ 		return eah;
+ 	}
+ 
+ 	/*
+ 	 * If it's a non-writable expanded array, copy it, extracting the element
+ 	 * representational data to save a catalog lookup.
+ 	 */
+ 	if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(d)))
+ 	{
+ 		eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
+ 		Assert(eah->ea_magic == EA_MAGIC);
+ 		d = expand_array(d, CurrentMemoryContext,
+ 						 eah->typlen, eah->typbyval, eah->typalign);
+ 		return (ExpandedArrayHeader *) DatumGetEOHP(d);
+ 	}
+ 
+ 	/* Else expand the hard way */
+ 	d = expand_array(d, CurrentMemoryContext, 0, 0, 0);
+ 	return (ExpandedArrayHeader *) DatumGetEOHP(d);
+ }
+ 
+ #define PG_GETARG_EXPANDED_ARRAY(n)  DatumGetExpandedArray(PG_GETARG_DATUM(n))
+ 
+ #endif
+ 
+ /*
+  * As above, when caller has the ability to cache element type info
+  */
+ static ExpandedArrayHeader *
+ DatumGetExpandedArrayX(Datum d,
+ 					   int elmlen, bool elmbyval, char elmalign)
+ {
+ 	ExpandedArrayHeader *eah;
+ 
+ 	/* If it's a writable expanded array already, just return it */
+ 	if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
+ 	{
+ 		eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
+ 		Assert(eah->ea_magic == EA_MAGIC);
+ 		Assert(eah->typlen == elmlen);
+ 		Assert(eah->typbyval == elmbyval);
+ 		Assert(eah->typalign == elmalign);
+ 		return eah;
+ 	}
+ 
+ 	/* Else expand using caller's data */
+ 	d = expand_array(d, CurrentMemoryContext, elmlen, elmbyval, elmalign);
+ 	return (ExpandedArrayHeader *) DatumGetEOHP(d);
+ }
+ 
+ #define PG_GETARG_EXPANDED_ARRAYX(n, elmlen, elmbyval, elmalign) \
+ 	DatumGetExpandedArrayX(PG_GETARG_DATUM(n),	elmlen, elmbyval, elmalign)
+ 
+ /*
+  * DatumGetAnyArray: return either an expanded array or a detoasted varlena
+  * array.  The result must not be modified in-place.
+  */
+ static 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);
+ }
+ 
+ #define PG_GETARG_ANY_ARRAY(n)	DatumGetAnyArray(PG_GETARG_DATUM(n))
+ 
+ /*
+  * Create the Datum/isnull representation if we didn't do so previously
+  */
+ static 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);
+ 	}
+ }
+ 
+ /*
+  * Equivalent of array_get_element() for an expanded array
+  */
+ 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
+ 	 * 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];
+ }
+ 
+ /*
+  * Equivalent of array_set_element() for an expanded array
+  *
+  * array_set_element took care of detoasting dataValue, the rest is up to us
+  *
+  * 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.
+  */
+ 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 */
+ 	if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(arraydatum)))
+ 		arraydatum = expand_array(arraydatum, CurrentMemoryContext,
+ 								  elmlen, elmbyval, elmalign);
+ 
+ 	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);
+ 
+ 	/*
+ 	 * 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;
+ 
+ 		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 PointerGetDatum(eah->hdr.eoh_rw_ptr);
+ }
+ 
+ /*
+  * Reimplementation of array_push for expanded arrays
+  */
+ typedef struct ArrayPushState
+ {
+ 	Oid			arg0_typeid;
+ 	Oid			arg1_typeid;
+ 	bool		array_on_left;
+ 	Oid			element_type;
+ 	int16		typlen;
+ 	bool		typbyval;
+ 	char		typalign;
+ } ArrayPushState;
+ 
+ Datum
+ array_push_expanded(PG_FUNCTION_ARGS)
+ {
+ 	ExpandedArrayHeader *eah;
+ 	Datum		result;
+ 	Datum		newelem;
+ 	bool		isNull;
+ 	int		   *dimv,
+ 			   *lb;
+ 	int			indx;
+ 	int			lb0;
+ 	Oid			element_type;
+ 	int16		typlen;
+ 	bool		typbyval;
+ 	char		typalign;
+ 	Oid			arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	Oid			arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ 	ArrayPushState *my_extra;
+ 
+ 	if (arg0_typeid == InvalidOid || arg1_typeid == InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("could not determine input data types")));
+ 
+ 	/*
+ 	 * We arrange to look up info about element type only once per series of
+ 	 * calls, assuming the element type doesn't change underneath us.
+ 	 */
+ 	my_extra = (ArrayPushState *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL)
+ 	{
+ 		fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 													  sizeof(ArrayPushState));
+ 		my_extra = (ArrayPushState *) fcinfo->flinfo->fn_extra;
+ 		my_extra->arg0_typeid = InvalidOid;
+ 	}
+ 
+ 	if (my_extra->arg0_typeid != arg0_typeid ||
+ 		my_extra->arg1_typeid != arg1_typeid)
+ 	{
+ 		/* Determine which input is the array */
+ 		Oid			arg0_elemid = get_element_type(arg0_typeid);
+ 		Oid			arg1_elemid = get_element_type(arg1_typeid);
+ 
+ 		if (arg0_elemid != InvalidOid)
+ 		{
+ 			my_extra->array_on_left = true;
+ 			element_type = arg0_elemid;
+ 		}
+ 		else if (arg1_elemid != InvalidOid)
+ 		{
+ 			my_extra->array_on_left = false;
+ 			element_type = arg1_elemid;
+ 		}
+ 		else
+ 		{
+ 			/* Shouldn't get here given proper type checking in parser */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("neither input type is an array")));
+ 			PG_RETURN_NULL();	/* keep compiler quiet */
+ 		}
+ 
+ 		my_extra->arg0_typeid = arg0_typeid;
+ 		my_extra->arg1_typeid = arg1_typeid;
+ 
+ 		/* Get info about element type */
+ 		get_typlenbyvalalign(element_type,
+ 							 &my_extra->typlen,
+ 							 &my_extra->typbyval,
+ 							 &my_extra->typalign);
+ 		my_extra->element_type = element_type;
+ 	}
+ 
+ 	element_type = my_extra->element_type;
+ 	typlen = my_extra->typlen;
+ 	typbyval = my_extra->typbyval;
+ 	typalign = my_extra->typalign;
+ 
+ 	/*
+ 	 * Now we can fetch the arguments, using cached type info if needed
+ 	 */
+ 	if (my_extra->array_on_left)
+ 	{
+ 		if (PG_ARGISNULL(0))
+ 			eah = construct_empty_expanded_array(element_type,
+ 												 CurrentMemoryContext,
+ 												 typlen, typbyval, typalign);
+ 		else
+ 			eah = PG_GETARG_EXPANDED_ARRAYX(0, typlen, typbyval, typalign);
+ 		isNull = PG_ARGISNULL(1);
+ 		if (isNull)
+ 			newelem = (Datum) 0;
+ 		else
+ 			newelem = PG_GETARG_DATUM(1);
+ 	}
+ 	else
+ 	{
+ 		if (PG_ARGISNULL(1))
+ 			eah = construct_empty_expanded_array(element_type,
+ 												 CurrentMemoryContext,
+ 												 typlen, typbyval, typalign);
+ 		else
+ 			eah = PG_GETARG_EXPANDED_ARRAYX(1, typlen, typbyval, typalign);
+ 		isNull = PG_ARGISNULL(0);
+ 		if (isNull)
+ 			newelem = (Datum) 0;
+ 		else
+ 			newelem = PG_GETARG_DATUM(0);
+ 	}
+ 
+ 	Assert(element_type == eah->element_type);
+ 
+ 	/*
+ 	 * Perform push (this logic is basically unchanged from original)
+ 	 */
+ 	if (eah->ndims == 1)
+ 	{
+ 		lb = eah->lbound;
+ 		dimv = eah->dims;
+ 
+ 		if (my_extra->array_on_left)
+ 		{
+ 			/* append newelem */
+ 			int			ub = dimv[0] + lb[0] - 1;
+ 
+ 			indx = ub + 1;
+ 			/* overflow? */
+ 			if (indx < ub)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 						 errmsg("integer out of range")));
+ 		}
+ 		else
+ 		{
+ 			/* prepend newelem */
+ 			indx = lb[0] - 1;
+ 			/* overflow? */
+ 			if (indx > lb[0])
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 						 errmsg("integer out of range")));
+ 		}
+ 		lb0 = lb[0];
+ 	}
+ 	else if (eah->ndims == 0)
+ 	{
+ 		indx = 1;
+ 		lb0 = 1;
+ 	}
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATA_EXCEPTION),
+ 				 errmsg("argument must be empty or one-dimensional array")));
+ 
+ 	result = array_set_element_expanded(PointerGetDatum(eah->hdr.eoh_rw_ptr),
+ 										1, &indx, newelem, isNull,
+ 										-1, typlen, typbyval, typalign);
+ 
+ 	Assert(result == PointerGetDatum(eah->hdr.eoh_rw_ptr));
+ 
+ 	/*
+ 	 * Readjust result's LB to match the input's.  We need do nothing in the
+ 	 * append case, but it's the simplest way to implement the prepend case.
+ 	 */
+ 	if (eah->ndims == 1 && !my_extra->array_on_left)
+ 	{
+ 		/* This is ok whether we've deconstructed or not */
+ 		eah->lbound[0] = lb0;
+ 	}
+ 
+ 	PG_RETURN_DATUM(result);
+ }
+ 
+ /*
+  * array_dims :
+  *		  returns the dimensions of the array pointed to by "v", as a "text"
+  *
+  * This is here as an example of handling either flat or expanded inputs.
+  */
+ Datum
+ array_dims_expanded(PG_FUNCTION_ARGS)
+ {
+ 	AnyArrayType *v = PG_GETARG_ANY_ARRAY(0);
+ 	char	   *p;
+ 	int			i;
+ 	int		   *dimv,
+ 			   *lb;
+ 
+ 	/*
+ 	 * 33 since we assume 15 digits per number + ':' +'[]'
+ 	 *
+ 	 * +1 for trailing null
+ 	 */
+ 	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);
+ 	}
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text(buf));
+ }
+ 
+ /*
+  * array_lower :
+  *		returns the lower dimension, of the DIM requested, for
+  *		the array pointed to by "v", as an int4
+  *
+  * This is here as an example of handling either flat or expanded inputs.
+  */
+ Datum
+ array_lower_expanded(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_upper :
+  *		returns the upper dimension, of the DIM requested, for
+  *		the array pointed to by "v", as an int4
+  *
+  * This is here as an example of handling either flat or expanded inputs.
+  */
+ Datum
+ array_upper_expanded(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;
+ 
+ 	PG_RETURN_INT32(result);
+ }
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 600646e..7eee40c 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
***************
*** 25,30 ****
--- 25,33 ----
  Datum
  array_push(PG_FUNCTION_ARGS)
  {
+ #if 1
+ 	return array_push_expanded(fcinfo);
+ #else
  	ArrayType  *v;
  	Datum		newelem;
  	bool		isNull;
*************** array_push(PG_FUNCTION_ARGS)
*** 157,162 ****
--- 160,166 ----
  		ARR_LBOUND(result)[0] = ARR_LBOUND(v)[0];
  
  	PG_RETURN_ARRAYTYPE_P(result);
+ #endif
  }
  
  /*-----------------------------------------------------------------------------
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 79aefaf..de107b6 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
***************
*** 27,32 ****
--- 27,33 ----
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/datum.h"
+ #include "utils/expandeddatum.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/typcache.h"
*************** 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);
--- 94,99 ----
*************** 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,
--- 936,942 ----
   * 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,
*************** array_ndims(PG_FUNCTION_ARGS)
*** 1666,1671 ****
--- 1663,1671 ----
  Datum
  array_dims(PG_FUNCTION_ARGS)
  {
+ #if 1
+ 	return array_dims_expanded(fcinfo);
+ #else
  	ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
  	char	   *p;
  	int			i;
*************** array_dims(PG_FUNCTION_ARGS)
*** 1694,1699 ****
--- 1694,1700 ----
  	}
  
  	PG_RETURN_TEXT_P(cstring_to_text(buf));
+ #endif
  }
  
  /*
*************** array_dims(PG_FUNCTION_ARGS)
*** 1704,1709 ****
--- 1705,1713 ----
  Datum
  array_lower(PG_FUNCTION_ARGS)
  {
+ #if 1
+ 	return array_lower_expanded(fcinfo);
+ #else
  	ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
  	int			reqdim = PG_GETARG_INT32(1);
  	int		   *lb;
*************** array_lower(PG_FUNCTION_ARGS)
*** 1721,1726 ****
--- 1725,1731 ----
  	result = lb[reqdim - 1];
  
  	PG_RETURN_INT32(result);
+ #endif
  }
  
  /*
*************** array_lower(PG_FUNCTION_ARGS)
*** 1731,1736 ****
--- 1736,1744 ----
  Datum
  array_upper(PG_FUNCTION_ARGS)
  {
+ #if 1
+ 	return array_upper_expanded(fcinfo);
+ #else
  	ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
  	int			reqdim = PG_GETARG_INT32(1);
  	int		   *dimv,
*************** array_upper(PG_FUNCTION_ARGS)
*** 1751,1756 ****
--- 1759,1765 ----
  	result = dimv[reqdim - 1] + lb[reqdim - 1] - 1;
  
  	PG_RETURN_INT32(result);
+ #endif
  }
  
  /*
*************** array_get_element(Datum arraydatum,
*** 1850,1855 ****
--- 1859,1876 ----
  		arraydataptr = (char *) DatumGetPointer(arraydatum);
  		arraynullsptr = NULL;
  	}
+ 	else if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ 	{
+ 		/* hand off to array_expanded.c */
+ 		return array_get_element_expanded(arraydatum,
+ 										  nSubscripts,
+ 										  indx,
+ 										  arraytyplen,
+ 										  elmlen,
+ 										  elmbyval,
+ 										  elmalign,
+ 										  isNull);
+ 	}
  	else
  	{
  		/* detoast input array if necessary */
*************** 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
--- 2104,2112 ----
   *
   * 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 ****
--- 2189,2206 ----
  	if (elmlen == -1 && !isNull)
  		dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue));
  
+ 	/* if array is in expanded form, hand off to array_expanded.c */
+ 	if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
+ 		return array_set_element_expanded(arraydatum,
+ 										  nSubscripts,
+ 										  indx,
+ 										  dataValue,
+ 										  isNull,
+ 										  arraytyplen,
+ 										  elmlen,
+ 										  elmbyval,
+ 										  elmalign);
+ 
  	/* detoast input array if necessary */
  	array = DatumGetArrayTypeP(arraydatum);
  
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 ...d43f437 .
*** a/src/backend/utils/adt/expandeddatum.c
--- b/src/backend/utils/adt/expandeddatum.c
***************
*** 0 ****
--- 1,162 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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));
+ 	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 PointerGetDatum(eohptr->eoh_ro_ptr);
+ }
+ 
+ /*
+  * 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 PointerGetDatum(eohptr->eoh_rw_ptr);
+ }
+ 
+ /*
+  * 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 202bc78..4b24066 100644
*** a/src/backend/utils/mmgr/mcxt.c
--- b/src/backend/utils/mmgr/mcxt.c
*************** MemoryContextSetParent(MemoryContext con
*** 266,271 ****
--- 266,275 ----
  	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/executor.h b/src/include/executor/executor.h
index 40fde83..a98a7af 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern void FreeExprContext(ExprContext 
*** 312,318 ****
  extern void ReScanExprContext(ExprContext *econtext);
  
  #define ResetExprContext(econtext) \
! 	MemoryContextReset((econtext)->ecxt_per_tuple_memory)
  
  extern ExprContext *MakePerTupleExprContext(EState *estate);
  
--- 312,318 ----
  extern void ReScanExprContext(ExprContext *econtext);
  
  #define ResetExprContext(econtext) \
! 	MemoryContextResetAndDeleteChildren((econtext)->ecxt_per_tuple_memory)
  
  extern ExprContext *MakePerTupleExprContext(EState *estate);
  
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 1d06f42..932a96b 100644
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct WindowFunc
*** 310,315 ****
--- 310,319 ----
   * 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 082c75b..5dd897a 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 dff69eb..1f18161 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern Datum array_remove(PG_FUNCTION_AR
*** 248,253 ****
--- 248,262 ----
  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 Datum array_agg_array_transfn(PG_
*** 356,361 ****
--- 365,390 ----
  extern Datum array_agg_array_finalfn(PG_FUNCTION_ARGS);
  
  /*
+  * prototypes for functions defined in array_expanded.c
+  */
+ extern Datum expand_array(Datum arraydatum, MemoryContext parentcontext,
+ 			 int elmlen, bool elmbyval, char elmalign);
+ extern Datum array_get_element_expanded(Datum arraydatum,
+ 						   int nSubscripts, int *indx,
+ 						   int arraytyplen,
+ 						   int elmlen, bool elmbyval, char elmalign,
+ 						   bool *isNull);
+ extern Datum array_set_element_expanded(Datum arraydatum,
+ 						   int nSubscripts, int *indx,
+ 						   Datum dataValue, bool isNull,
+ 						   int arraytyplen,
+ 						   int elmlen, bool elmbyval, char elmalign);
+ extern Datum array_push_expanded(PG_FUNCTION_ARGS);
+ extern Datum array_dims_expanded(PG_FUNCTION_ARGS);
+ extern Datum array_lower_expanded(PG_FUNCTION_ARGS);
+ extern Datum array_upper_expanded(PG_FUNCTION_ARGS);
+ 
+ /*
   * prototypes for functions defined in array_typanalyze.c
   */
  extern Datum array_typanalyze(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 ...584d0c6 .
*** a/src/include/utils/expandeddatum.h
--- b/src/include/utils/expandeddatum.h
***************
*** 0 ****
--- 1,146 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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 macro 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 for cases where the argument
+  * fetching code will return either a fully-uncompressed flat object or a
+  * 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.
+  * (Some of these might be worth inlining later.)
+  */
+ 
+ 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 f364ce4..d021145 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** build_datatype(HeapTuple typeTup, int32 
*** 2202,2207 ****
--- 2202,2223 ----
  	typ->typbyval = typeStruct->typbyval;
  	typ->typrelid = typeStruct->typrelid;
  	typ->typioparam = getTypeIOParam(typeTup);
+ 	/* 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->collation = typeStruct->typcollation;
  	if (OidIsValid(collation) && OidIsValid(typ->collation))
  		typ->collation = collation;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b7e3bc4..25ca236 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 32,37 ****
--- 32,38 ----
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/datum.h"
+ #include "utils/expandeddatum.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
*************** static void exec_assign_value(PLpgSQL_ex
*** 171,176 ****
--- 172,178 ----
  				  Datum value, Oid valtype, bool *isNull);
  static void exec_eval_datum(PLpgSQL_execstate *estate,
  				PLpgSQL_datum *datum,
+ 				bool getrwpointer,
  				Oid *typeid,
  				int32 *typetypmod,
  				Datum *value,
*************** plpgsql_exec_function(PLpgSQL_function *
*** 295,300 ****
--- 297,332 ----
  					var->value = fcinfo->arg[i];
  					var->isnull = fcinfo->argnull[i];
  					var->freeval = false;
+ 
+ 					/*
+ 					 * Hack to force array parameters into expanded form.
+ 					 * 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.
+ 					 */
+ 					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,
+ 													  0, 0, 0);
+ 							var->freeval = true;
+ 						}
+ 					}
  				}
  				break;
  
*************** plpgsql_exec_function(PLpgSQL_function *
*** 461,478 ****
  
  			/*
  			 * 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);
! 			}
  		}
  	}
  
--- 493,506 ----
  
  			/*
  			 * 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_assign_value(PLpgSQL_execstate *est
*** 4061,4086 ****
  				/*
  				 * 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;
  			}
  
--- 4089,4139 ----
  				/*
  				 * 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 (!var->datatype->typbyval && !*isNull)
! 				{
! 					newvalue = datumTransfer(newvalue,
! 											 false,
! 											 var->datatype->typlen);
! 
! 					/*
! 					 * If it's an array, force the value to be stored in
! 					 * expanded form.  This wins if the function later does,
! 					 * eg, a lot of array subscripting operations on the
! 					 * variable, and otherwise might lose badly.  We might
! 					 * need to use a different heuristic, but it's too soon to
! 					 * tell.  Also, what of cases where it'd be useful to
! 					 * force non-array values into expanded form?
! 					 */
! 					if (var->datatype->typisarray &&
! 						!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(newvalue)))
! 					{
! 						Datum		avalue;
! 
! 						avalue = expand_array(newvalue, CurrentMemoryContext,
! 											  0, 0, 0);
! 						pfree(DatumGetPointer(newvalue));
! 						newvalue = avalue;
! 					}
! 				}
  
  				/*
! 				 * 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
*** 4277,4283 ****
  				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
  
  				/* Fetch current value of array datum */
! 				exec_eval_datum(estate, target,
  								&parenttypoid, &parenttypmod,
  								&oldarraydatum, &oldarrayisnull);
  
--- 4330,4336 ----
  				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
  
  				/* Fetch current value of array datum */
! 				exec_eval_datum(estate, target, true,
  								&parenttypoid, &parenttypmod,
  								&oldarraydatum, &oldarrayisnull);
  
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4423,4438 ****
   *
   * The type oid, typmod, value in Datum format, and null flag are returned.
   *
   * 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,
  				PLpgSQL_datum *datum,
  				Oid *typeid,
  				int32 *typetypmod,
  				Datum *value,
--- 4476,4495 ----
   *
   * The type oid, typmod, value in Datum format, and null flag are returned.
   *
+  * If getrwpointer is TRUE, we'll return a R/W pointer to any variable that
+  * is an expanded object; otherwise we return a R/O pointer.
+  *
   * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
   *
!  * NOTE: in most cases 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,
  				PLpgSQL_datum *datum,
+ 				bool getrwpointer,
  				Oid *typeid,
  				int32 *typetypmod,
  				Datum *value,
*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4448,4454 ****
  
  				*typeid = var->datatype->typoid;
  				*typetypmod = var->datatype->atttypmod;
! 				*value = var->value;
  				*isnull = var->isnull;
  				break;
  			}
--- 4505,4516 ----
  
  				*typeid = var->datatype->typoid;
  				*typetypmod = var->datatype->atttypmod;
! 				if (getrwpointer)
! 					*value = var->value;
! 				else
! 					*value = MakeExpandedObjectReadOnly(var->value,
! 														var->isnull,
! 													  var->datatype->typlen);
  				*isnull = var->isnull;
  				break;
  			}
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5284,5290 ****
  				PLpgSQL_var *var = (PLpgSQL_var *) datum;
  				ParamExternData *prm = &paramLI->params[dno];
  
! 				prm->value = var->value;
  				prm->isnull = var->isnull;
  				prm->pflags = PARAM_FLAG_CONST;
  				prm->ptype = var->datatype->typoid;
--- 5346,5354 ----
  				PLpgSQL_var *var = (PLpgSQL_var *) datum;
  				ParamExternData *prm = &paramLI->params[dno];
  
! 				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
*** 5350,5356 ****
  	/* OK, evaluate the value and store into the appropriate paramlist slot */
  	datum = estate->datums[dno];
  	prm = &params->params[dno];
! 	exec_eval_datum(estate, datum,
  					&prm->ptype, &prmtypmod,
  					&prm->value, &prm->isnull);
  }
--- 5414,5420 ----
  	/* OK, evaluate the value and store into the appropriate paramlist slot */
  	datum = estate->datums[dno];
  	prm = &params->params[dno];
! 	exec_eval_datum(estate, datum, false,
  					&prm->ptype, &prmtypmod,
  					&prm->value, &prm->isnull);
  }
*************** make_tuple_from_row(PLpgSQL_execstate *e
*** 5542,5548 ****
  		if (row->varnos[i] < 0) /* should not happen */
  			elog(ERROR, "dropped rowtype entry for non-dropped column");
  
! 		exec_eval_datum(estate, estate->datums[row->varnos[i]],
  						&fieldtypeid, &fieldtypmod,
  						&dvalues[i], &nulls[i]);
  		if (fieldtypeid != tupdesc->attrs[i]->atttypid)
--- 5606,5612 ----
  		if (row->varnos[i] < 0) /* should not happen */
  			elog(ERROR, "dropped rowtype entry for non-dropped column");
  
! 		exec_eval_datum(estate, estate->datums[row->varnos[i]], false,
  						&fieldtypeid, &fieldtypmod,
  						&dvalues[i], &nulls[i]);
  		if (fieldtypeid != tupdesc->attrs[i]->atttypid)
*************** free_var(PLpgSQL_var *var)
*** 6335,6341 ****
  {
  	if (var->freeval)
  	{
! 		pfree(DatumGetPointer(var->value));
  		var->freeval = false;
  	}
  }
--- 6399,6410 ----
  {
  	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
*** 6542,6549 ****
  
  		curvar = (PLpgSQL_var *) estate->datums[dno];
  
! 		exec_eval_datum(estate, (PLpgSQL_datum *) curvar, &paramtypeid,
! 						&paramtypmod, &paramdatum, &paramisnull);
  
  		appendStringInfo(&paramstr, "%s%s = ",
  						 paramno > 0 ? ", " : "",
--- 6611,6619 ----
  
  		curvar = (PLpgSQL_var *) estate->datums[dno];
  
! 		exec_eval_datum(estate, (PLpgSQL_datum *) curvar, false,
! 						&paramtypeid, &paramtypmod,
! 						&paramdatum, &paramisnull);
  
  		appendStringInfo(&paramstr, "%s%s = ",
  						 paramno > 0 ? ", " : "",
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 00f2f77..c95087c 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct
*** 180,185 ****
--- 180,186 ----
  	bool		typbyval;
  	Oid			typrelid;
  	Oid			typioparam;
+ 	bool		typisarray;		/* is "true" array, or domain over one */
  	Oid			collation;		/* from pg_type, but can be overridden */
  	FmgrInfo	typinput;		/* lookup info for typinput function */
  	int32		atttypmod;		/* typmod (taken from someplace else) */
