memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
 }
 
+/* ----------------
+ *     heap_copy_tuple_as_datum
+ *
+ *     copy a tuple as a composite-type Datum
+ * ----------------
+ */
+Datum
+heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
+{
+   HeapTupleHeader td;
+
+   /*
+    * If the tuple contains any external TOAST pointers, we have to inline
+    * those fields to meet the conventions for composite-type Datums.
+    */
+   if (HeapTupleHasExternal(tuple))
+       return toast_flatten_tuple_to_datum(tuple->t_data,
+                                           tuple->t_len,
+                                           tupleDesc);
+
+   /*
+    * Fast path for easy case: just make a palloc'd copy and insert the
+    * correct composite-Datum header fields (since those may not be set if
+    * the given tuple came from disk, rather than from heap_form_tuple).
+    */
+   td = (HeapTupleHeader) palloc(tuple->t_len);
+   memcpy((char *) td, (char *) tuple->t_data, tuple->t_len);
+
+   HeapTupleHeaderSetDatumLength(td, tuple->t_len);
+   HeapTupleHeaderSetTypeId(td, tupleDesc->tdtypeid);
+   HeapTupleHeaderSetTypMod(td, tupleDesc->tdtypmod);
+
+   return PointerGetDatum(td);
+}
+
 /*
  * heap_form_tuple
  *     construct a tuple from the given values[] and isnull[] arrays,
                data_len;
    int         hoff;
    bool        hasnull = false;
-   Form_pg_attribute *att = tupleDescriptor->attrs;
    int         numberOfAttributes = tupleDescriptor->natts;
    int         i;
 
                        numberOfAttributes, MaxTupleAttributeNumber)));
 
    /*
-    * Check for nulls and embedded tuples; expand any toasted attributes in
-    * embedded tuples.  This preserves the invariant that toasting can only
-    * go one level deep.
-    *
-    * We can skip calling toast_flatten_tuple_attribute() if the attribute
-    * couldn't possibly be of composite type.  All composite datums are
-    * varlena and have alignment 'd'; furthermore they aren't arrays. Also,
-    * if an attribute is already toasted, it must have been sent to disk
-    * already and so cannot contain toasted attributes.
+    * Check for nulls
     */
    for (i = 0; i < numberOfAttributes; i++)
    {
        if (isnull[i])
-           hasnull = true;
-       else if (att[i]->attlen == -1 &&
-                att[i]->attalign == 'd' &&
-                att[i]->attndims == 0 &&
-                !VARATT_IS_EXTENDED(DatumGetPointer(values[i])))
        {
-           values[i] = toast_flatten_tuple_attribute(values[i],
-                                                     att[i]->atttypid,
-                                                     att[i]->atttypmod);
+           hasnull = true;
+           break;
        }
    }
 
 
    /*
     * And fill in the information.  Note we fill the Datum fields even though
-    * this tuple may never become a Datum.
+    * this tuple may never become a Datum.  This lets HeapTupleHeaderGetDatum
+    * identify the tuple type if needed.
     */
    tuple->t_len = len;
    ItemPointerSetInvalid(&(tuple->t_self));
                data_len;
    int         hoff;
    bool        hasnull = false;
-   Form_pg_attribute *att = tupleDescriptor->attrs;
    int         numberOfAttributes = tupleDescriptor->natts;
    int         i;
 
                        numberOfAttributes, MaxTupleAttributeNumber)));
 
    /*
-    * Check for nulls and embedded tuples; expand any toasted attributes in
-    * embedded tuples.  This preserves the invariant that toasting can only
-    * go one level deep.
-    *
-    * We can skip calling toast_flatten_tuple_attribute() if the attribute
-    * couldn't possibly be of composite type.  All composite datums are
-    * varlena and have alignment 'd'; furthermore they aren't arrays. Also,
-    * if an attribute is already toasted, it must have been sent to disk
-    * already and so cannot contain toasted attributes.
+    * Check for nulls
     */
    for (i = 0; i < numberOfAttributes; i++)
    {
        if (isnull[i])
-           hasnull = true;
-       else if (att[i]->attlen == -1 &&
-                att[i]->attalign == 'd' &&
-                att[i]->attndims == 0 &&
-                !VARATT_IS_EXTENDED(values[i]))
        {
-           values[i] = toast_flatten_tuple_attribute(values[i],
-                                                     att[i]->atttypid,
-                                                     att[i]->atttypmod);
+           hasnull = true;
+           break;
        }
    }
 
 
    if (tupmask & HEAP_HASVARWIDTH)
        infomask |= INDEX_VAR_MASK;
 
+   /* Also assert we got rid of external attributes */
+#ifdef TOAST_INDEX_HACK
+   Assert((tupmask & HEAP_HASEXTERNAL) == 0);
+#endif
+
    /*
     * Here we make sure that the size will fit in the field reserved for it
     * in t_info.
 
  *
  * "Flatten" a tuple to contain no out-of-line toasted fields.
  * (This does not eliminate compressed or short-header datums.)
+ *
+ * Note: we expect the caller already checked HeapTupleHasExternal(tup),
+ * so there is no need for a short-circuit path.
  * ----------
  */
 HeapTuple
 
 
 /* ----------
- * toast_flatten_tuple_attribute -
+ * toast_flatten_tuple_to_datum -
+ *
+ * "Flatten" a tuple containing out-of-line toasted fields into a Datum.
+ * The result is always palloc'd in the current memory context.
+ *
+ * We have a general rule that Datums of container types (rows, arrays,
+ * ranges, etc) must not contain any external TOAST pointers.  Without
+ * this rule, we'd have to look inside each Datum when preparing a tuple
+ * for storage, which would be expensive and would fail to extend cleanly
+ * to new sorts of container types.
+ *
+ * However, we don't want to say that tuples represented as HeapTuples
+ * can't contain toasted fields, so instead this routine should be called
+ * when such a HeapTuple is being converted into a Datum.
  *
- * If a Datum is of composite type, "flatten" it to contain no toasted fields.
- * This must be invoked on any potentially-composite field that is to be
- * inserted into a tuple.  Doing this preserves the invariant that toasting
- * goes only one level deep in a tuple.
+ * While we're at it, we decompress any compressed fields too.  This is not
+ * necessary for correctness, but reflects an expectation that compression
+ * will be more effective if applied to the whole tuple not individual
+ * fields.  We are not so concerned about that that we want to deconstruct
+ * and reconstruct tuples just to get rid of compressed fields, however.
+ * So callers typically won't call this unless they see that the tuple has
+ * at least one external field.
  *
- * Note that flattening does not mean expansion of short-header varlenas,
- * so in one sense toasting is allowed within composite datums.
+ * On the other hand, in-line short-header varlena fields are left alone.
+ * If we "untoasted" them here, they'd just get changed back to short-header
+ * format anyway within heap_fill_tuple.
  * ----------
  */
 Datum
-toast_flatten_tuple_attribute(Datum value,
-                             Oid typeId, int32 typeMod)
+toast_flatten_tuple_to_datum(HeapTupleHeader tup,
+                            uint32 tup_len,
+                            TupleDesc tupleDesc)
 {
-   TupleDesc   tupleDesc;
-   HeapTupleHeader olddata;
    HeapTupleHeader new_data;
    int32       new_header_len;
    int32       new_data_len;
    int32       new_tuple_len;
    HeapTupleData tmptup;
-   Form_pg_attribute *att;
-   int         numAttrs;
+   Form_pg_attribute *att = tupleDesc->attrs;
+   int         numAttrs = tupleDesc->natts;
    int         i;
-   bool        need_change = false;
    bool        has_nulls = false;
    Datum       toast_values[MaxTupleAttributeNumber];
    bool        toast_isnull[MaxTupleAttributeNumber];
    bool        toast_free[MaxTupleAttributeNumber];
 
-   /*
-    * See if it's a composite type, and get the tupdesc if so.
-    */
-   tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true);
-   if (tupleDesc == NULL)
-       return value;           /* not a composite type */
-
-   att = tupleDesc->attrs;
-   numAttrs = tupleDesc->natts;
-
-   /*
-    * Break down the tuple into fields.
-    */
-   olddata = DatumGetHeapTupleHeader(value);
-   Assert(typeId == HeapTupleHeaderGetTypeId(olddata));
-   Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
    /* Build a temporary HeapTuple control structure */
-   tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
+   tmptup.t_len = tup_len;
    ItemPointerSetInvalid(&(tmptup.t_self));
    tmptup.t_tableOid = InvalidOid;
-   tmptup.t_data = olddata;
+   tmptup.t_data = tup;
 
+   /*
+    * Break down the tuple into fields.
+    */
    Assert(numAttrs <= MaxTupleAttributeNumber);
    heap_deform_tuple(&tmptup, tupleDesc, toast_values, toast_isnull);
 
                new_value = heap_tuple_untoast_attr(new_value);
                toast_values[i] = PointerGetDatum(new_value);
                toast_free[i] = true;
-               need_change = true;
            }
        }
    }
 
-   /*
-    * If nothing to untoast, just return the original tuple.
-    */
-   if (!need_change)
-   {
-       ReleaseTupleDesc(tupleDesc);
-       return value;
-   }
-
    /*
     * Calculate the new size of the tuple.
     *
    new_header_len = offsetof(HeapTupleHeaderData, t_bits);
    if (has_nulls)
        new_header_len += BITMAPLEN(numAttrs);
-   if (olddata->t_infomask & HEAP_HASOID)
+   if (tup->t_infomask & HEAP_HASOID)
        new_header_len += sizeof(Oid);
    new_header_len = MAXALIGN(new_header_len);
    new_data_len = heap_compute_data_size(tupleDesc,
    /*
     * Copy the existing tuple header, but adjust natts and t_hoff.
     */
-   memcpy(new_data, olddata, offsetof(HeapTupleHeaderData, t_bits));
+   memcpy(new_data, tup, offsetof(HeapTupleHeaderData, t_bits));
    HeapTupleHeaderSetNatts(new_data, numAttrs);
    new_data->t_hoff = new_header_len;
-   if (olddata->t_infomask & HEAP_HASOID)
-       HeapTupleHeaderSetOid(new_data, HeapTupleHeaderGetOid(olddata));
+   if (tup->t_infomask & HEAP_HASOID)
+       HeapTupleHeaderSetOid(new_data, HeapTupleHeaderGetOid(tup));
 
-   /* Reset the datum length field, too */
+   /* Set the composite-Datum header fields correctly */
    HeapTupleHeaderSetDatumLength(new_data, new_tuple_len);
+   HeapTupleHeaderSetTypeId(new_data, tupleDesc->tdtypeid);
+   HeapTupleHeaderSetTypMod(new_data, tupleDesc->tdtypmod);
 
    /* Copy over the data, and fill the null bitmap if needed */
    heap_fill_tuple(tupleDesc,
    for (i = 0; i < numAttrs; i++)
        if (toast_free[i])
            pfree(DatumGetPointer(toast_values[i]));
-   ReleaseTupleDesc(tupleDesc);
 
    return PointerGetDatum(new_data);
 }
 
 {
    Var        *variable = (Var *) wrvstate->xprstate.expr;
    TupleTableSlot *slot;
-   HeapTuple   tuple;
-   TupleDesc   tupleDesc;
    HeapTupleHeader dtuple;
 
    if (isDone)
    if (wrvstate->wrv_junkFilter != NULL)
        slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
 
-   tuple = ExecFetchSlotTuple(slot);
-   tupleDesc = slot->tts_tupleDescriptor;
-
    /*
-    * We have to make a copy of the tuple so we can safely insert the Datum
-    * overhead fields, which are not set in on-disk tuples.
+    * Copy the slot tuple and make sure any toasted fields get detoasted.
     */
-   dtuple = (HeapTupleHeader) palloc(tuple->t_len);
-   memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
-
-   HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+   dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
 
    /*
-    * If the Var identifies a named composite type, label the tuple with that
-    * type; otherwise use what is in the tupleDesc.
+    * If the Var identifies a named composite type, label the datum with that
+    * type; otherwise we'll use the slot's info.
     */
    if (variable->vartype != RECORDOID)
    {
        HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
        HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
    }
-   else
-   {
-       HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid);
-       HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod);
-   }
 
    return PointerGetDatum(dtuple);
 }
    }
 
    /*
-    * We have to make a copy of the tuple so we can safely insert the Datum
-    * overhead fields, which are not set in on-disk tuples.
+    * Copy the slot tuple and make sure any toasted fields get detoasted.
     */
-   dtuple = (HeapTupleHeader) palloc(tuple->t_len);
-   memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
+   dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
 
-   HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
+   /*
+    * Reset datum's type ID fields to match the Var.
+    */
    HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
    HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
 
 
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
  *     ExecFetchSlotTupleDatum
  *         Fetch the slot's tuple as a composite-type Datum.
  *
- *     We convert the slot's contents to local physical-tuple form,
- *     and fill in the Datum header fields.  Note that the result
- *     always points to storage owned by the slot.
+ *     The result is always freshly palloc'd in the caller's memory context.
  * --------------------------------
  */
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
    HeapTuple   tup;
-   HeapTupleHeader td;
    TupleDesc   tupdesc;
 
-   /* Make sure we can scribble on the slot contents ... */
-   tup = ExecMaterializeSlot(slot);
-   /* ... and set up the composite-Datum header fields, in case not done */
-   td = tup->t_data;
+   /* Fetch slot's contents in regular-physical-tuple form */
+   tup = ExecFetchSlotTuple(slot);
    tupdesc = slot->tts_tupleDescriptor;
-   HeapTupleHeaderSetDatumLength(td, tup->t_len);
-   HeapTupleHeaderSetTypeId(td, tupdesc->tdtypeid);
-   HeapTupleHeaderSetTypMod(td, tupdesc->tdtypmod);
-   return PointerGetDatum(td);
+
+   /* Convert to Datum form */
+   return heap_copy_tuple_as_datum(tup, tupdesc);
 }
 
 /* --------------------------------
    return tuple;
 }
 
+/*
+ * HeapTupleHeaderGetDatum - convert a HeapTupleHeader pointer to a Datum.
+ *
+ * This must *not* get applied to an on-disk tuple; the tuple should be
+ * freshly made by heap_form_tuple or some wrapper routine for it (such as
+ * BuildTupleFromCStrings).  Be sure also that the tupledesc used to build
+ * the tuple has a properly "blessed" rowtype.
+ *
+ * Formerly this was a macro equivalent to PointerGetDatum, relying on the
+ * fact that heap_form_tuple fills in the appropriate tuple header fields
+ * for a composite Datum.  However, we now require that composite Datums not
+ * contain any external TOAST pointers.  We do not want heap_form_tuple itself
+ * to enforce that; more specifically, the rule applies only to actual Datums
+ * and not to HeapTuple structures.  Therefore, HeapTupleHeaderGetDatum is
+ * now a function that detects whether there are externally-toasted fields
+ * and constructs a new tuple with inlined fields if so.  We still need
+ * heap_form_tuple to insert the Datum header fields, because otherwise this
+ * code would have no way to obtain a tupledesc for the tuple.
+ *
+ * Note that if we do build a new tuple, it's palloc'd in the current
+ * memory context. Beware of code that changes context between the initial
+ * heap_form_tuple/etc call and calling HeapTuple(Header)GetDatum.
+ *
+ * For performance-critical callers, it could be worthwhile to take extra
+ * steps to ensure that there aren't TOAST pointers in the output of
+ * heap_form_tuple to begin with.  It's likely however that the costs of the
+ * typcache lookup and tuple disassembly/reassembly are swamped by TOAST
+ * dereference costs, so that the benefits of such extra effort would be
+ * minimal.
+ *
+ * XXX it would likely be better to create wrapper functions that produce
+ * a composite Datum from the field values in one step.  However, there's
+ * enough code using the existing APIs that we couldn't get rid of this
+ * hack anytime soon.
+ */
+Datum
+HeapTupleHeaderGetDatum(HeapTupleHeader tuple)
+{
+   Datum       result;
+   TupleDesc   tupDesc;
+
+   /* No work if there are no external TOAST pointers in the tuple */
+   if (!HeapTupleHeaderHasExternal(tuple))
+       return PointerGetDatum(tuple);
+
+   /* Use the type data saved by heap_form_tuple to look up the rowtype */
+   tupDesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(tuple),
+                                    HeapTupleHeaderGetTypMod(tuple));
+
+   /* And do the flattening */
+   result = toast_flatten_tuple_to_datum(tuple,
+                                       HeapTupleHeaderGetDatumLength(tuple),
+                                         tupDesc);
+
+   ReleaseTupleDesc(tupDesc);
+
+   return result;
+}
+
+
 /*
  * Functions for sending tuples to the frontend (or other specified destination)
  * as though it is a SELECT result. These are used by utility commands that
 
        /* We must return the whole tuple as a Datum. */
        fcinfo->isnull = false;
        value = ExecFetchSlotTupleDatum(slot);
-       value = datumCopy(value, fcache->typbyval, fcache->typlen);
    }
    else
    {
 
        oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
    }
 
-   dtup = (HeapTupleHeader) palloc(tuple->t_len);
-   memcpy((char *) dtup, (char *) tuple->t_data, tuple->t_len);
-
-   HeapTupleHeaderSetDatumLength(dtup, tuple->t_len);
-   HeapTupleHeaderSetTypeId(dtup, tupdesc->tdtypeid);
-   HeapTupleHeaderSetTypMod(dtup, tupdesc->tdtypmod);
+   dtup = DatumGetHeapTupleHeader(heap_copy_tuple_as_datum(tuple, tupdesc));
 
    if (oldcxt)
        MemoryContextSwitchTo(oldcxt);
 
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "catalog/pg_type.h"
+#include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
    (tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \
 )
 
+#define HeapTupleHeaderHasExternal(tup) \
+       (((tup)->t_infomask & HEAP_HASEXTERNAL) != 0)
+
 
 /*
  * BITMAPLEN(NATTS) -
                bool *isnull);
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
+extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
                Datum *values, bool *isnull);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
 
 extern HeapTuple toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc);
 
 /* ----------
- * toast_flatten_tuple_attribute -
+ * toast_flatten_tuple_to_datum -
  *
- * If a Datum is of composite type, "flatten" it to contain no toasted fields.
- * This must be invoked on any potentially-composite field that is to be
- * inserted into a tuple.  Doing this preserves the invariant that toasting
- * goes only one level deep in a tuple.
+ * "Flatten" a tuple containing out-of-line toasted fields into a Datum.
  * ----------
  */
-extern Datum toast_flatten_tuple_attribute(Datum value,
-                             Oid typeId, int32 typeMod);
+extern Datum toast_flatten_tuple_to_datum(HeapTupleHeader tup,
+                            uint32 tup_len,
+                            TupleDesc tupleDesc);
 
 /* ----------
  * toast_compress_datum -
 
 #define PG_RETURN_TEXT_P(x)    PG_RETURN_POINTER(x)
 #define PG_RETURN_BPCHAR_P(x)  PG_RETURN_POINTER(x)
 #define PG_RETURN_VARCHAR_P(x) PG_RETURN_POINTER(x)
-#define PG_RETURN_HEAPTUPLEHEADER(x)  PG_RETURN_POINTER(x)
+#define PG_RETURN_HEAPTUPLEHEADER(x)  return HeapTupleHeaderGetDatum(x)
 
 
 /*-------------------------------------------------------------------------
 
  * HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) -
  *     build a HeapTuple given user data in C string form. values is an array
  *     of C strings, one for each attribute of the return tuple.
+ * Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple) - convert a
+ *     HeapTupleHeader to a Datum.
  *
  * Macro declarations:
  * HeapTupleGetDatum(HeapTuple tuple) - convert a HeapTuple to a Datum.
  *----------
  */
 
-#define HeapTupleGetDatum(_tuple)      PointerGetDatum((_tuple)->t_data)
+#define HeapTupleGetDatum(tuple)       HeapTupleHeaderGetDatum((tuple)->t_data)
 /* obsolete version of above */
-#define TupleGetDatum(_slot, _tuple)   PointerGetDatum((_tuple)->t_data)
+#define TupleGetDatum(_slot, _tuple)   HeapTupleGetDatum(_tuple)
 
 extern TupleDesc RelationNameGetTupleDesc(const char *relname);
 extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 extern TupleDesc BlessTupleDesc(TupleDesc tupdesc);
 extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc);
 extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values);
+extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 
 
 
                tup = make_tuple_from_row(estate, row, row->rowtupdesc);
                if (tup == NULL)    /* should not happen */
                    elog(ERROR, "row not compatible with its own tupdesc");
-               MemoryContextSwitchTo(oldcontext);
                *typeid = row->rowtupdesc->tdtypeid;
                *typetypmod = row->rowtupdesc->tdtypmod;
                *value = HeapTupleGetDatum(tup);
                *isnull = false;
+               MemoryContextSwitchTo(oldcontext);
                break;
            }
 
        case PLPGSQL_DTYPE_REC:
            {
                PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
-               HeapTupleData worktup;
 
                if (!HeapTupleIsValid(rec->tup))
                    ereport(ERROR,
                /* Make sure we have a valid type/typmod setting */
                BlessTupleDesc(rec->tupdesc);
 
-               /*
-                * In a trigger, the NEW and OLD parameters are likely to be
-                * on-disk tuples that don't have the desired Datum fields.
-                * Copy the tuple body and insert the right values.
-                */
                oldcontext = MemoryContextSwitchTo(estate->eval_econtext->ecxt_per_tuple_memory);
-               heap_copytuple_with_tuple(rec->tup, &worktup);
-               HeapTupleHeaderSetDatumLength(worktup.t_data, worktup.t_len);
-               HeapTupleHeaderSetTypeId(worktup.t_data, rec->tupdesc->tdtypeid);
-               HeapTupleHeaderSetTypMod(worktup.t_data, rec->tupdesc->tdtypmod);
-               MemoryContextSwitchTo(oldcontext);
                *typeid = rec->tupdesc->tdtypeid;
                *typetypmod = rec->tupdesc->tdtypmod;
-               *value = HeapTupleGetDatum(&worktup);
+               *value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
                *isnull = false;
+               MemoryContextSwitchTo(oldcontext);
                break;
            }
 
 
  [5:5]={"(42,43)"}
 (1 row)
 
+-- Check that arrays of composites are safely detoasted when needed
+create temp table src (f1 text);
+insert into src
+  select string_agg(random()::text,'') from generate_series(1,10000);
+create type textandtext as (c1 text, c2 text);
+create temp table dest (f1 textandtext[]);
+insert into dest select array[row(f1,f1)::textandtext] from src;
+select length(md5((f1[1]).c2)) from dest;
+ length 
+--------
+     32
+(1 row)
+
+delete from src;
+select length(md5((f1[1]).c2)) from dest;
+ length 
+--------
+     32
+(1 row)
+
+truncate table src;
+drop table src;
+select length(md5((f1[1]).c2)) from dest;
+ length 
+--------
+     32
+(1 row)
+
+drop table dest;
+drop type textandtext;
 
 
    MemoryContextSwitchTo(old_context);
 
-   PG_RETURN_HEAPTUPLEHEADER(newtup->t_data);
+   /*
+    * We intentionally don't use PG_RETURN_HEAPTUPLEHEADER here, because that
+    * would cause the indirect toast pointers to be flattened out of the
+    * tuple immediately, rendering subsequent testing irrelevant.  So just
+    * return the HeapTupleHeader pointer as-is.  This violates the general
+    * rule that composite Datums shouldn't contain toast pointers, but so
+    * long as the regression test scripts don't insert the result of this
+    * function into a container type (record, array, etc) it should be OK.
+    */
+   PG_RETURN_POINTER(newtup->t_data);
 }
 
 select * from t1;
 update t1 set f1[5].q2 = 43;
 select * from t1;
+
+-- Check that arrays of composites are safely detoasted when needed
+
+create temp table src (f1 text);
+insert into src
+  select string_agg(random()::text,'') from generate_series(1,10000);
+create type textandtext as (c1 text, c2 text);
+create temp table dest (f1 textandtext[]);
+insert into dest select array[row(f1,f1)::textandtext] from src;
+select length(md5((f1[1]).c2)) from dest;
+delete from src;
+select length(md5((f1[1]).c2)) from dest;
+truncate table src;
+drop table src;
+select length(md5((f1[1]).c2)) from dest;
+drop table dest;
+drop type textandtext;