Re: Composite Datums containing toasted fields are a bad idea(?) - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Composite Datums containing toasted fields are a bad idea(?)
Date
Msg-id 6110.1398022703@sss.pgh.pa.us
Whole thread Raw
In response to Re: Composite Datums containing toasted fields are a bad idea(?)  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Composite Datums containing toasted fields are a bad idea(?)
Re: Composite Datums containing toasted fields are a bad idea(?)
List pgsql-hackers
I wrote:
> The main problem with this patch, as I see it, is that it'll introduce
> extra syscache lookup overhead even when there are no toasted fields
> anywhere.  I've not really tried to quantify how much, since that would
> require first agreeing on a benchmark case --- anybody have a thought
> about what that ought to be?  But in at least some of these functions,
> we'll be paying a cache lookup or two per array element, which doesn't
> sound promising.

I created a couple of test cases that I think are close to worst-case for
accumArrayResult() and array_set(), which are the two functions that seem
most worth worrying about.  The accumArrayResult test case is

create table manycomplex as
select row(random(),random())::complex as c
from generate_series(1,1000000);
vacuum analyze manycomplex;

then time:

select array_dims(array_agg(c)) from manycomplex;

On HEAD, this takes about 295-300 msec on my machine in a non-cassert
build.  With the patch I sent previously, the time goes to 495-500 msec.
Ouch.

The other test case is

create or replace function timearrayset(n int) returns void language plpgsql as
$$
declare a complex[];
begin
a := array[row(1,2), row(3,4), row(5,6)];
for i in 1..$1 loop
   a[2] := row(5,6)::complex;
end loop;
end;
$$;

select timearrayset(1000000);

This goes from circa 1250 ms in HEAD to around 1680 ms.

Some poking around with oprofile says that much of the added time is
coming from added syscache and typcache lookups, as I suspected.

I did some further work on the patch to make it possible to cache
the element tuple descriptor across calls for these two functions.
With the attached patch, the first test case comes down to about 335 ms
and the second to about 1475 ms (plpgsql is still leaving some extra
cache lookups on the table).  More could be done with some further API
changes, but I think this is about as much as is safe to back-patch.

At least in the accumArrayResult test case, it would be possible to
bring the runtime back to very close to HEAD if we were willing to
abandon the principle that compressed fields within a tuple should
always get decompressed when the tuple goes into a larger structure.
That would allow flatten_composite_element to quit early if the
tuple doesn't have the HEAP_HASEXTERNAL infomask bit set.  I'm not
sure that this would be a good tradeoff however: if we fail to remove nested
compression, we could end up paying for that with double compression.
On the other hand, it's unclear that that case would come up often,
while the overhead of disassembling the tuple is definitely going to
happen every time we assign to an array-of-composites element.  (Also,
there is no question here of regression relative to previous releases,
since the existing code fails to prevent nested compression.)

Comments?

            regards, tom lane

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index aea9d40..48b09b8 100644
*** a/src/backend/access/common/heaptuple.c
--- b/src/backend/access/common/heaptuple.c
*************** heap_form_tuple(TupleDesc tupleDescripto
*** 649,674 ****
       * 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.
       */
      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);
!         }
      }

      /*
--- 649,661 ----
       * 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.
       */
      for (i = 0; i < numberOfAttributes; i++)
      {
          if (isnull[i])
              hasnull = true;
!         else
!             values[i] = toast_flatten_tuple_attribute(values[i], att[i]);
      }

      /*
*************** heap_form_minimal_tuple(TupleDesc tupleD
*** 1403,1428 ****
       * 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.
       */
      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);
!         }
      }

      /*
--- 1390,1402 ----
       * 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.
       */
      for (i = 0; i < numberOfAttributes; i++)
      {
          if (isnull[i])
              hasnull = true;
!         else
!             values[i] = toast_flatten_tuple_attribute(values[i], att[i]);
      }

      /*
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 9a821d3..2d1a922 100644
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
*************** toast_insert_or_update(Relation rel, Hea
*** 991,996 ****
--- 991,999 ----
   *
   *    "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(HeapTuple tup, Tuple
*** 1068,1097 ****


  /* ----------
!  * toast_flatten_tuple_attribute -
   *
!  *    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.
   *
!  *    Note that flattening does not mean expansion of short-header varlenas,
!  *    so in one sense toasting is allowed within composite datums.
   * ----------
   */
  Datum
! toast_flatten_tuple_attribute(Datum value,
!                               Oid typeId, int32 typeMod)
  {
-     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;
      int            i;
      bool        need_change = false;
      bool        has_nulls = false;
--- 1071,1110 ----


  /* ----------
!  * toast_flatten_tuple_datum -
   *
!  *    "Flatten" a composite Datum to contain no toasted fields.
!  *    This must be invoked on any composite value that is to be inserted into
!  *    a tuple, array, range, etc.  Doing this preserves the invariant that
!  *    toasting goes only one level deep in a tuple.
   *
!  *    Unlike toast_flatten_tuple, this does expand compressed fields.  That's
!  *    not necessary for correctness, but is a policy decision based on the
!  *    assumption that compression will be more effective if applied to the
!  *    whole tuple not individual fields.
!  *
!  *    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.
!  *
!  *    Note: generally speaking, callers should skip applying this to Datums
!  *    that are toasted overall, even just to the extent of being in short-header
!  *    format.  That implies that the Datum has previously been stored within
!  *    some tuple, array, range, etc, and therefore it should already not contain
!  *    any out-of-line fields.
   * ----------
   */
  Datum
! toast_flatten_tuple_datum(Datum value, 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 = tupleDesc->attrs;
!     int            numAttrs = tupleDesc->natts;
      int            i;
      bool        need_change = false;
      bool        has_nulls = false;
*************** toast_flatten_tuple_attribute(Datum valu
*** 1100,1120 ****
      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);
      ItemPointerSetInvalid(&(tmptup.t_self));
--- 1113,1128 ----
      bool        toast_free[MaxTupleAttributeNumber];

      /*
       * Break down the tuple into fields.
+      *
+      * Note: if the supplied datum is toasted overall, DatumGetHeapTupleHeader
+      * will detoast it, and then we'll leak the detoasted copy.  This is not
+      * worth fixing because callers shouldn't call us on toasted datums
+      * anyway.
       */
      olddata = DatumGetHeapTupleHeader(value);
!     Assert(HeapTupleHeaderGetTypeId(olddata) == tupleDesc->tdtypeid);
!     Assert(HeapTupleHeaderGetTypMod(olddata) == tupleDesc->tdtypmod);
      /* Build a temporary HeapTuple control structure */
      tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
      ItemPointerSetInvalid(&(tmptup.t_self));
*************** toast_flatten_tuple_attribute(Datum valu
*** 1150,1162 ****
      }

      /*
!      * If nothing to untoast, just return the original tuple.
       */
      if (!need_change)
-     {
-         ReleaseTupleDesc(tupleDesc);
          return value;
-     }

      /*
       * Calculate the new size of the tuple.
--- 1158,1167 ----
      }

      /*
!      * If nothing to untoast, just return the original datum.
       */
      if (!need_change)
          return value;

      /*
       * Calculate the new size of the tuple.
*************** toast_flatten_tuple_attribute(Datum valu
*** 1202,1214 ****
      for (i = 0; i < numAttrs; i++)
          if (toast_free[i])
              pfree(DatumGetPointer(toast_values[i]));
-     ReleaseTupleDesc(tupleDesc);

      return PointerGetDatum(new_data);
  }


  /* ----------
   * toast_compress_datum -
   *
   *    Create a compressed version of a varlena datum
--- 1207,1269 ----
      for (i = 0; i < numAttrs; i++)
          if (toast_free[i])
              pfree(DatumGetPointer(toast_values[i]));

      return PointerGetDatum(new_data);
  }


  /* ----------
+  * toast_flatten_tuple_attribute -
+  *
+  *    If a Datum is of composite type, "flatten" it to contain no toasted fields.
+  *    This is a convenience routine for doing toast_flatten_tuple_datum() on
+  *    tuple fields.
+  * ----------
+  */
+ Datum
+ toast_flatten_tuple_attribute(Datum value, Form_pg_attribute att)
+ {
+     TupleDesc    tupleDesc;
+
+     /*
+      * Exit quickly if the attribute couldn't possibly be of composite type.
+      * All composite datums are varlena and have alignment 'd'; furthermore
+      * they aren't arrays.  This heuristic is sufficient to eliminate all
+      * but a few non-composite types.
+      */
+     if (att->attlen != -1 ||
+         att->attalign != 'd' ||
+         att->attndims != 0)
+         return value;
+
+     /*
+      * Also, if the datum is already toasted, it must have already been stored
+      * within some larger tuple (or array, range, etc) and so it doesn't need
+      * to be flattened again.  (It is not our job here to force it to be
+      * uncompressed, so don't worry about that.)
+      */
+     if (VARATT_IS_EXTENDED(DatumGetPointer(value)))
+         return value;
+
+     /*
+      * See if it's a composite type, and get the tupdesc if so.
+      */
+     tupleDesc = lookup_rowtype_tupdesc_noerror(att->atttypid,
+                                                att->atttypmod,
+                                                true);
+     if (tupleDesc == NULL)
+         return value;            /* not a composite type */
+
+     /* OK, flatten it */
+     value = toast_flatten_tuple_datum(value, tupleDesc);
+
+     ReleaseTupleDesc(tupleDesc);
+
+     return value;
+ }
+
+
+ /* ----------
   * toast_compress_datum -
   *
   *    Create a compressed version of a varlena datum
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 0eba025..d84c051 100644
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
*************** ExecEvalArrayRef(ArrayRefExprState *asta
*** 449,462 ****
          }

          if (lIndex == NULL)
!             resultArray = array_set(array_source, i,
!                                     upper.indx,
!                                     sourceData,
!                                     eisnull,
!                                     astate->refattrlength,
!                                     astate->refelemlength,
!                                     astate->refelembyval,
!                                     astate->refelemalign);
          else
              resultArray = array_set_slice(array_source, i,
                                            upper.indx, lower.indx,
--- 449,492 ----
          }

          if (lIndex == NULL)
!         {
!             /*
!              * If element type is composite, look up the tupledesc.  This is
!              * not because array_set_element couldn't do so, but because we
!              * need to install the ShutdownTupleDescRef callback to clean up
!              * the tupledesc reference later, and we don't have another easy
!              * way to remember if that's been done already.
!              */
!             if (astate->refiscomposite && !eisnull)
!             {
!                 HeapTupleHeader tuple;
!                 Oid            tupType;
!                 int32        tupTypmod;
!
!                 tuple = DatumGetHeapTupleHeader(sourceData);
!
!                 tupType = HeapTupleHeaderGetTypeId(tuple);
!                 tupTypmod = HeapTupleHeaderGetTypMod(tuple);
!
!                 (void) get_cached_rowtype(tupType, tupTypmod,
!                                           &astate->refelemtupdesc,
!                                           econtext);
!
!                 /* If we detoasted sourceData overall, pass that on */
!                 sourceData = PointerGetDatum(tuple);
!             }
!
!             resultArray = array_set_element(array_source, i,
!                                             upper.indx,
!                                             sourceData,
!                                             eisnull,
!                                             astate->refattrlength,
!                                             astate->refelemlength,
!                                             astate->refelembyval,
!                                             astate->refelemalign,
!                                             astate->refiscomposite,
!                                             &astate->refelemtupdesc);
!         }
          else
              resultArray = array_set_slice(array_source, i,
                                            upper.indx, lower.indx,
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4510,4519 ****
                                                      parent);
                  /* do one-time catalog lookups for type info */
                  astate->refattrlength = get_typlen(aref->refarraytype);
!                 get_typlenbyvalalign(aref->refelemtype,
!                                      &astate->refelemlength,
!                                      &astate->refelembyval,
!                                      &astate->refelemalign);
                  state = (ExprState *) astate;
              }
              break;
--- 4540,4551 ----
                                                      parent);
                  /* do one-time catalog lookups for type info */
                  astate->refattrlength = get_typlen(aref->refarraytype);
!                 get_typlenbyvalaligncomp(aref->refelemtype,
!                                          &astate->refelemlength,
!                                          &astate->refelembyval,
!                                          &astate->refelemalign,
!                                          &astate->refiscomposite);
!                 astate->refelemtupdesc = NULL;
                  state = (ExprState *) astate;
              }
              break;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 91df184..c52a5de 100644
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
***************
*** 17,22 ****
--- 17,23 ----
  #include <ctype.h>

  #include "access/htup_details.h"
+ #include "access/tuptoaster.h"
  #include "funcapi.h"
  #include "libpq/pqformat.h"
  #include "utils/array.h"
*************** typedef struct ArrayIteratorData
*** 74,79 ****
--- 75,130 ----
      int            current_item;    /* the item # we're at in the array */
  }    ArrayIteratorData;

+ /* Support for array element detoasting */
+
+ /* Initialize state needed by FLATTEN_ARRAY_ELEMENT */
+ #define INIT_FLATTEN(tupdesc) \
+     do { \
+         tupdesc = NULL; \
+     } while (0)
+
+ /* Call this on each non-NULL value about to be inserted into an array */
+ #define FLATTEN_ARRAY_ELEMENT(val, typlen, typbyval, typiscomposite, tupdesc) \
+     do { \
+         if ((typlen) == -1) \
+         { \
+             if (typiscomposite) \
+                 val = flatten_composite_element(val, &(tupdesc)); \
+             else \
+                 val = PointerGetDatum(PG_DETOAST_DATUM(val)); \
+         } \
+     } while (0)
+
+ /* Variant that guarantees to make a copy */
+ #define FLATTEN_ARRAY_ELEMENT_COPY(val, typlen, typbyval, typiscomposite, tupdesc) \
+     do { \
+         if ((typlen) == -1) \
+         { \
+             if (typiscomposite) \
+             { \
+                 Datum _orig_val = (val); \
+                 val = flatten_composite_element(val, &(tupdesc)); \
+                 if (val == _orig_val) \
+                     val = datumCopy(val, typbyval, typlen); \
+             } \
+             else \
+                 val = PointerGetDatum(PG_DETOAST_DATUM_COPY(val)); \
+         } \
+         else \
+             val = datumCopy(val, typbyval, typlen); \
+     } while (0)
+
+ /* Clean up after some FLATTEN_ARRAY_ELEMENT calls */
+ #define END_FLATTEN(tupdesc) \
+     do { \
+         if (tupdesc) \
+         { \
+             ReleaseTupleDesc(tupdesc); \
+             tupdesc = NULL; \
+         } \
+     } while (0)
+
+ /* Local functions */
  static bool array_isspace(char ch);
  static int    ArrayCount(const char *str, int *dim, char typdelim);
  static void ReadArrayStr(char *arrayStr, const char *origStr,
*************** static void CopyArrayEls(ArrayType *arra
*** 92,97 ****
--- 143,151 ----
               Datum *values, bool *nulls, int nitems,
               int typlen, bool typbyval, char typalign,
               bool freedata);
+ static int    array_cmp(FunctionCallInfo fcinfo);
+ static bool element_type_is_composite(Oid elmtype, int16 typlen, char typalign);
+ static Datum flatten_composite_element(Datum value, TupleDesc *tupdescptr);
  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);
*************** static void array_insert_slice(ArrayType
*** 119,125 ****
                     int ndim, int *dim, int *lb,
                     int *st, int *endp,
                     int typlen, bool typbyval, char typalign);
- static int    array_cmp(FunctionCallInfo fcinfo);
  static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbv, int nbytes,
                        Oid elmtype, int dataoffset);
  static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs,
--- 173,178 ----
*************** ReadArrayStr(char *arrayStr,
*** 864,870 ****
              hasnull = true;
          else
          {
!             /* let's just make sure data is not toasted */
              if (typlen == -1)
                  values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
              totbytes = att_addlength_datum(totbytes, typlen, values[i]);
--- 917,930 ----
              hasnull = true;
          else
          {
!             /*
!              * Let's just make sure data is not toasted.  It seems quite
!              * unlikely that the input function could have produced a toasted
!              * value at all, and certainly it shouldn't have produced
!              * something containing nested toasted values, so we don't bother
!              * with FLATTEN_ARRAY_ELEMENT().  But a PG_DETOAST_DATUM() call is
!              * reasonably cheap when it's a no-op.
!              */
              if (typlen == -1)
                  values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
              totbytes = att_addlength_datum(totbytes, typlen, values[i]);
*************** ReadArrayBinary(StringInfo buf,
*** 1466,1472 ****
              hasnull = true;
          else
          {
!             /* let's just make sure data is not toasted */
              if (typlen == -1)
                  values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
              totbytes = att_addlength_datum(totbytes, typlen, values[i]);
--- 1526,1539 ----
              hasnull = true;
          else
          {
!             /*
!              * Let's just make sure data is not toasted.  It seems quite
!              * unlikely that the receive function could have produced a
!              * toasted value at all, and certainly it shouldn't have produced
!              * something containing nested toasted values, so we don't bother
!              * with FLATTEN_ARRAY_ELEMENT().  But a PG_DETOAST_DATUM() call is
!              * reasonably cheap when it's a no-op.
!              */
              if (typlen == -1)
                  values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
              totbytes = att_addlength_datum(totbytes, typlen, values[i]);
*************** array_get_slice(ArrayType *array,
*** 2019,2025 ****
  }

  /*
!  * array_set :
   *          This routine sets the value of an array element (specified by
   *          a subscript array) to a new value specified by "dataValue".
   *
--- 2086,2092 ----
  }

  /*
!  * array_set_element :
   *          This routine sets the value of an array element (specified by
   *          a subscript array) to a new value specified by "dataValue".
   *
*************** array_get_slice(ArrayType *array,
*** 2035,2045 ****
--- 2102,2117 ----
   *    elmlen: pg_type.typlen for the array's element type
   *    elmbyval: pg_type.typbyval for the array's element type
   *    elmalign: pg_type.typalign for the array's element type
+  *    elmiscomposite: true if element type is composite
+  *    element_tupdesc: pointer to where to cache tupledesc, if composite
   *
   * Result:
   *          A new array is returned, just like the old except for the one
   *          modified entry.  The original array object is not changed.
   *
+  * Caller must arrange for any refcount on *element_tupdesc to be released
+  * when no longer useful.
+  *
   * For one-dimensional arrays only, we allow the array to be extended
   * by assigning to a position outside the existing subscript range; any
   * positions between the existing elements and the new one are set to NULLs.
*************** array_get_slice(ArrayType *array,
*** 2049,2063 ****
   * rather than returning a NULL as the fetch operations do.
   */
  ArrayType *
! array_set(ArrayType *array,
!           int nSubscripts,
!           int *indx,
!           Datum dataValue,
!           bool isNull,
!           int arraytyplen,
!           int elmlen,
!           bool elmbyval,
!           char elmalign)
  {
      ArrayType  *newarray;
      int            i,
--- 2121,2137 ----
   * rather than returning a NULL as the fetch operations do.
   */
  ArrayType *
! array_set_element(ArrayType *array,
!                   int nSubscripts,
!                   int *indx,
!                   Datum dataValue,
!                   bool isNull,
!                   int arraytyplen,
!                   int elmlen,
!                   bool elmbyval,
!                   char elmalign,
!                   bool elmiscomposite,
!                   TupleDesc *element_tupdesc)
  {
      ArrayType  *newarray;
      int            i,
*************** array_set(ArrayType *array,
*** 2116,2122 ****

      /* make sure item to be inserted is not toasted */
      if (elmlen == -1 && !isNull)
!         dataValue = PointerGetDatum(PG_DETOAST_DATUM(dataValue));

      /* detoast input array if necessary */
      array = DatumGetArrayTypeP(PointerGetDatum(array));
--- 2190,2201 ----

      /* make sure item to be inserted is not toasted */
      if (elmlen == -1 && !isNull)
!     {
!         FLATTEN_ARRAY_ELEMENT(dataValue,
!                               elmlen, elmbyval,
!                               elmiscomposite,
!                               *element_tupdesc);
!     }

      /* detoast input array if necessary */
      array = DatumGetArrayTypeP(PointerGetDatum(array));
*************** array_set(ArrayType *array,
*** 2306,2311 ****
--- 2385,2446 ----
  }

  /*
+  * array_set :
+  *        Backwards-compatibility function that doesn't require caller
+  *        to supply info about composite element types.
+  *
+  * Identical to array_set_element except elmiscomposite is computed internally
+  * and there is no chance to cache element tupledesc across calls.
+  */
+ ArrayType *
+ array_set(ArrayType *array,
+           int nSubscripts,
+           int *indx,
+           Datum dataValue,
+           bool isNull,
+           int arraytyplen,
+           int elmlen,
+           bool elmbyval,
+           char elmalign)
+ {
+     ArrayType  *newarray;
+     bool        elmiscomposite;
+     TupleDesc    elmtupdesc;
+
+     if (arraytyplen < 0)
+     {
+         Oid            elmtype;
+
+         /* detoast input array if necessary */
+         array = DatumGetArrayTypeP(PointerGetDatum(array));
+         /* now we can get the element type safely */
+         elmtype = ARR_ELEMTYPE(array);
+
+         elmiscomposite = element_type_is_composite(elmtype, elmlen, elmalign);
+     }
+     else
+         elmiscomposite = false; /* not possible for fixed-size array */
+
+     INIT_FLATTEN(elmtupdesc);
+
+     newarray = array_set_element(array,
+                                  nSubscripts,
+                                  indx,
+                                  dataValue,
+                                  isNull,
+                                  arraytyplen,
+                                  elmlen,
+                                  elmbyval,
+                                  elmalign,
+                                  elmiscomposite,
+                                  &elmtupdesc);
+
+     END_FLATTEN(elmtupdesc);
+
+     return newarray;
+ }
+
+ /*
   * array_set_slice :
   *          This routine sets the value of a range of array locations (specified
   *          by upper and lower subscript values) to new values passed as
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2683,2688 ****
--- 2818,2825 ----
      int            typlen;
      bool        typbyval;
      char        typalign;
+     bool        typiscomposite;
+     TupleDesc    typtupdesc;
      char       *s;
      bits8       *bitmap;
      int            bitmask;
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2741,2746 ****
--- 2878,2886 ----
      typbyval = ret_extra->typbyval;
      typalign = ret_extra->typalign;

+     typiscomposite = element_type_is_composite(retType, typlen, typalign);
+     INIT_FLATTEN(typtupdesc);
+
      /* Allocate temporary arrays for new values */
      values = (Datum *) palloc(nitems * sizeof(Datum));
      nulls = (bool *) palloc(nitems * sizeof(bool));
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2800,2807 ****
          else
          {
              /* Ensure data is not toasted */
!             if (typlen == -1)
!                 values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
              /* Update total result size */
              nbytes = att_addlength_datum(nbytes, typlen, values[i]);
              nbytes = att_align_nominal(nbytes, typalign);
--- 2940,2948 ----
          else
          {
              /* Ensure data is not toasted */
!             FLATTEN_ARRAY_ELEMENT(values[i],
!                                   typlen, typbyval,
!                                   typiscomposite, typtupdesc);
              /* Update total result size */
              nbytes = att_addlength_datum(nbytes, typlen, values[i]);
              nbytes = att_align_nominal(nbytes, typalign);
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2825,2830 ****
--- 2966,2973 ----
          }
      }

+     END_FLATTEN(typtupdesc);
+
      /* Allocate and initialize the result array */
      if (hasnulls)
      {
*************** array_map(FunctionCallInfo fcinfo, Oid i
*** 2868,2874 ****
   * A palloc'd 1-D array object is constructed and returned.  Note that
   * elem values will be copied into the object even if pass-by-ref type.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
   * from the system catalogs, given the elmtype.  However, the caller is
   * in a better position to cache this info across multiple uses, or even
   * to hard-wire values if the element type is hard-wired.
--- 3011,3017 ----
   * A palloc'd 1-D array object is constructed and returned.  Note that
   * elem values will be copied into the object even if pass-by-ref type.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbyval/elmalign info
   * from the system catalogs, given the elmtype.  However, the caller is
   * in a better position to cache this info across multiple uses, or even
   * to hard-wire values if the element type is hard-wired.
*************** construct_array(Datum *elems, int nelems
*** 2902,2908 ****
   * A palloc'd ndims-D array object is constructed and returned.  Note that
   * elem values will be copied into the object even if pass-by-ref type.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
   * from the system catalogs, given the elmtype.  However, the caller is
   * in a better position to cache this info across multiple uses, or even
   * to hard-wire values if the element type is hard-wired.
--- 3045,3051 ----
   * A palloc'd ndims-D array object is constructed and returned.  Note that
   * elem values will be copied into the object even if pass-by-ref type.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbyval/elmalign info
   * from the system catalogs, given the elmtype.  However, the caller is
   * in a better position to cache this info across multiple uses, or even
   * to hard-wire values if the element type is hard-wired.
*************** construct_md_array(Datum *elems,
*** 2921,2926 ****
--- 3064,3071 ----
      int32        dataoffset;
      int            i;
      int            nelems;
+     bool        elmiscomposite;
+     TupleDesc    elmtupdesc;

      if (ndims < 0)                /* we do allow zero-dimension arrays */
          ereport(ERROR,
*************** construct_md_array(Datum *elems,
*** 2938,2943 ****
--- 3083,3091 ----

      nelems = ArrayGetNItems(ndims, dims);

+     elmiscomposite = element_type_is_composite(elmtype, elmlen, elmalign);
+     INIT_FLATTEN(elmtupdesc);
+
      /* compute required space */
      nbytes = 0;
      hasnulls = false;
*************** construct_md_array(Datum *elems,
*** 2949,2956 ****
              continue;
          }
          /* make sure data is not toasted */
!         if (elmlen == -1)
!             elems[i] = PointerGetDatum(PG_DETOAST_DATUM(elems[i]));
          nbytes = att_addlength_datum(nbytes, elmlen, elems[i]);
          nbytes = att_align_nominal(nbytes, elmalign);
          /* check for overflow of total request */
--- 3097,3106 ----
              continue;
          }
          /* make sure data is not toasted */
!         FLATTEN_ARRAY_ELEMENT(elems[i],
!                               elmlen, elmbyval,
!                               elmiscomposite, elmtupdesc);
!         /* update total result size */
          nbytes = att_addlength_datum(nbytes, elmlen, elems[i]);
          nbytes = att_align_nominal(nbytes, elmalign);
          /* check for overflow of total request */
*************** construct_md_array(Datum *elems,
*** 2961,2966 ****
--- 3111,3118 ----
                              (int) MaxAllocSize)));
      }

+     END_FLATTEN(elmtupdesc);
+
      /* Allocate and initialize result array */
      if (hasnulls)
      {
*************** construct_empty_array(Oid elmtype)
*** 3020,3026 ****
   * If array elements are pass-by-ref data type, the returned Datums will
   * be pointers into the array object.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbval/elmalign info
   * from the system catalogs, given the elmtype.  However, in most current
   * uses the type is hard-wired into the caller and so we can save a lookup
   * cycle by hard-wiring the type info as well.
--- 3172,3178 ----
   * If array elements are pass-by-ref data type, the returned Datums will
   * be pointers into the array object.
   *
!  * NOTE: it would be cleaner to look up the elmlen/elmbyval/elmalign info
   * from the system catalogs, given the elmtype.  However, in most current
   * uses the type is hard-wired into the caller and so we can save a lookup
   * cycle by hard-wiring the type info as well.
*************** array_free_iterator(ArrayIterator iterat
*** 4070,4075 ****
--- 4222,4328 ----
  /***************************************************************************/

  /*
+  * Array element detoasting ("flattening") support
+  *
+  * It is critical that no external TOAST pointers appear within an array,
+  * either directly as an array element or within a non-scalar element type,
+  * so we must detoast any value that is about to be inserted into an array.
+  *
+  * Since we allow composite-type datums to contain toasted fields (at the
+  * first level; nested toasted values aren't allowed), we have to work extra
+  * hard to get rid of possible toasting when the array element type is
+  * composite.  We assume that other composites such as ranges contain no
+  * external pointers, though.
+  *
+  * In addition to the no-external-pointers requirement, we make a policy
+  * decision that array elements not be compressed.    We expect that compression
+  * would be better applied to the whole array rather than individual elements.
+  *
+  * The current implementation of FLATTEN_ARRAY_ELEMENT() also forces varlena
+  * elements to not be in short-header format, but this is an implementation
+  * artifact that should probably be removed.
+  */
+
+ /*
+  * Check if array element type is composite.
+  *
+  * Currently, we only have typlen/typbyval/typalign information cached for
+  * the array element type.    To minimize unnecessary syscache lookups, this
+  * function uses the knowledge that a composite type must have typlen -1
+  * (varlena) and alignment 'd'.  There are only a few non-composite, non-array
+  * types for which this is true, so though imperfect this heuristic should
+  * work well enough.
+  *
+  * XXX ideally this would go away in favor of always getting typiscomposite
+  * info in the same syscache lookup that we get typlen etc with.  That will
+  * require additional API changes though, so it doesn't seem back-patchable.
+  */
+ static bool
+ element_type_is_composite(Oid elmtype, int16 typlen, char typalign)
+ {
+     if (typlen == -1 && typalign == 'd' && type_is_rowtype(elmtype))
+         return true;
+     else
+         return false;
+ }
+
+ /*
+  * Detoast ("flatten") a composite value
+  *
+  * This is the out-of-line portion of the FLATTEN_ARRAY_ELEMENT macro.
+  * We assume we have a non-null, known-composite value to work on.
+  * The tuple descriptor for the type can be cached at *tupdescptr.
+  *
+  * Note: for some logic paths, it's important that this does not
+  * copy the Datum if re-invoked on a previously flattened Datum.
+  */
+ static Datum
+ flatten_composite_element(Datum value, TupleDesc *tupdescptr)
+ {
+     HeapTupleHeader tupdata;
+     Oid            typeid;
+     int32        typmod;
+     TupleDesc    tupleDesc;
+
+     /*
+      * If the value is itself toasted (even just to the extent of having a
+      * short varlena header), then it must have been incorporated into some
+      * larger composite structure, so it should not contain any embedded toast
+      * pointers.  We need only detoast it overall and we're done.
+      */
+     if (VARATT_IS_EXTENDED(DatumGetPointer(value)))
+         return PointerGetDatum(PG_DETOAST_DATUM(value));
+
+     /*
+      * When working with an array of RECORD, it's possible that each array
+      * element is of a different record subtype, so we have to be prepared to
+      * look up the appropriate tupdesc here.  But we cache the tupdesc across
+      * calls as much as we can.  For an array of a named composite type, we
+      * should only have to do one lookup, but it seems prudent to check the
+      * type every time anyway.
+      */
+     tupdata = DatumGetHeapTupleHeader(value);
+     typeid = HeapTupleHeaderGetTypeId(tupdata);
+     typmod = HeapTupleHeaderGetTypMod(tupdata);
+
+     tupleDesc = *tupdescptr;
+     if (tupleDesc &&
+         (tupleDesc->tdtypeid != typeid || tupleDesc->tdtypmod != typmod))
+     {
+         ReleaseTupleDesc(tupleDesc);
+         tupleDesc = NULL;
+     }
+     if (tupleDesc == NULL)
+     {
+         tupleDesc = lookup_rowtype_tupdesc(typeid, typmod);
+         *tupdescptr = tupleDesc;
+     }
+
+     /* tuptoaster.c does the actual work */
+     return toast_flatten_tuple_datum(value, tupleDesc);
+ }
+
+ /*
   * Check whether a specific array element is NULL
   *
   * nullbitmap: pointer to array's null bitmap (NULL if none)
*************** accumArrayResult(ArrayBuildState *astate
*** 4591,4600 ****
          astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
          astate->nelems = 0;
          astate->element_type = element_type;
!         get_typlenbyvalalign(element_type,
!                              &astate->typlen,
!                              &astate->typbyval,
!                              &astate->typalign);
      }
      else
      {
--- 4844,4855 ----
          astate->dnulls = (bool *) palloc(astate->alen * sizeof(bool));
          astate->nelems = 0;
          astate->element_type = element_type;
!         get_typlenbyvalaligncomp(element_type,
!                                  &astate->typlen,
!                                  &astate->typbyval,
!                                  &astate->typalign,
!                                  &astate->typiscomposite);
!         INIT_FLATTEN(astate->element_tupdesc);
      }
      else
      {
*************** accumArrayResult(ArrayBuildState *astate
*** 4621,4630 ****
       */
      if (!disnull && !astate->typbyval)
      {
!         if (astate->typlen == -1)
!             dvalue = PointerGetDatum(PG_DETOAST_DATUM_COPY(dvalue));
!         else
!             dvalue = datumCopy(dvalue, astate->typbyval, astate->typlen);
      }

      astate->dvalues[astate->nelems] = dvalue;
--- 4876,4885 ----
       */
      if (!disnull && !astate->typbyval)
      {
!         FLATTEN_ARRAY_ELEMENT_COPY(dvalue,
!                                    astate->typlen, astate->typbyval,
!                                    astate->typiscomposite,
!                                    astate->element_tupdesc);
      }

      astate->dvalues[astate->nelems] = dvalue;
*************** makeMdArrayResult(ArrayBuildState *astat
*** 4691,4697 ****

      MemoryContextSwitchTo(oldcontext);

!     /* Clean up all the junk */
      if (release)
          MemoryContextDelete(astate->mcontext);

--- 4946,4959 ----

      MemoryContextSwitchTo(oldcontext);

!     /*
!      * Clean up all the junk.  We must release any tupdesc refcount held for
!      * composite-flattening purposes even if !release, since we can't be sure
!      * we'll get called again.  flatten_composite_element can re-acquire the
!      * refcount if needed.
!      */
!     END_FLATTEN(astate->element_tupdesc);
!
      if (release)
          MemoryContextDelete(astate->mcontext);

*************** array_fill_internal(ArrayType *dims, Arr
*** 5037,5043 ****

          /* make sure data is not toasted */
          if (elmlen == -1)
!             value = PointerGetDatum(PG_DETOAST_DATUM(value));

          nbytes = att_addlength_datum(0, elmlen, value);
          nbytes = att_align_nominal(nbytes, elmalign);
--- 5299,5315 ----

          /* make sure data is not toasted */
          if (elmlen == -1)
!         {
!             bool        elmiscomposite;
!             TupleDesc    elmtupdesc;
!
!             elmiscomposite = element_type_is_composite(elmtype, elmlen, elmalign);
!             INIT_FLATTEN(elmtupdesc);
!             FLATTEN_ARRAY_ELEMENT(value,
!                                   elmlen, elmbyval,
!                                   elmiscomposite, elmtupdesc);
!             END_FLATTEN(elmtupdesc);
!         }

          nbytes = att_addlength_datum(0, elmlen, value);
          nbytes = att_align_nominal(nbytes, elmalign);
*************** array_replace_internal(ArrayType *array,
*** 5277,5286 ****
       */
      if (typlen == -1)
      {
          if (!search_isnull)
!             search = PointerGetDatum(PG_DETOAST_DATUM(search));
          if (!replace_isnull)
!             replace = PointerGetDatum(PG_DETOAST_DATUM(replace));
      }

      /* Prepare to apply the comparison operator */
--- 5549,5569 ----
       */
      if (typlen == -1)
      {
+         bool        typiscomposite;
+         TupleDesc    typtupdesc;
+
+         typiscomposite = element_type_is_composite(element_type,
+                                                    typlen, typalign);
+         INIT_FLATTEN(typtupdesc);
          if (!search_isnull)
!             FLATTEN_ARRAY_ELEMENT(search,
!                                   typlen, typbyval,
!                                   typiscomposite, typtupdesc);
          if (!replace_isnull)
!             FLATTEN_ARRAY_ELEMENT(replace,
!                                   typlen, typbyval,
!                                   typiscomposite, typtupdesc);
!         END_FLATTEN(typtupdesc);
      }

      /* Prepare to apply the comparison operator */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a4ce716..278709d 100644
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** get_typlenbyvalalign(Oid typid, int16 *t
*** 1922,1927 ****
--- 1922,1953 ----
  }

  /*
+  * get_typlenbyvalaligncomp
+  *
+  *        A four-fer: given the type OID, return typlen, typbyval, typalign,
+  *        and typiscomposite.
+  */
+ void
+ get_typlenbyvalaligncomp(Oid typid, int16 *typlen, bool *typbyval,
+                          char *typalign, bool *typiscomposite)
+ {
+     HeapTuple    tp;
+     Form_pg_type typtup;
+
+     tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+     if (!HeapTupleIsValid(tp))
+         elog(ERROR, "cache lookup failed for type %u", typid);
+     typtup = (Form_pg_type) GETSTRUCT(tp);
+     *typlen = typtup->typlen;
+     *typbyval = typtup->typbyval;
+     *typalign = typtup->typalign;
+     /* this computation must match type_is_rowtype(): */
+     *typiscomposite = (typid == RECORDOID ||
+                        typtup->typtype == TYPTYPE_COMPOSITE);
+     ReleaseSysCache(tp);
+ }
+
+ /*
   * getTypeIOParam
   *        Given a pg_type row, select the type OID to pass to I/O functions
   *
diff --git a/src/include/access/tuptoaster.h b/src/include/access/tuptoaster.h
index 296d016..5cdefad 100644
*** a/src/include/access/tuptoaster.h
--- b/src/include/access/tuptoaster.h
*************** extern struct varlena *heap_tuple_untoas
*** 184,199 ****
  extern HeapTuple toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc);

  /* ----------
   * toast_flatten_tuple_attribute -
   *
   *    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.
   * ----------
   */
! extern Datum toast_flatten_tuple_attribute(Datum value,
!                               Oid typeId, int32 typeMod);

  /* ----------
   * toast_compress_datum -
--- 184,208 ----
  extern HeapTuple toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc);

  /* ----------
+  * toast_flatten_tuple_datum -
+  *
+  *    "Flatten" a composite Datum to contain no toasted fields.
+  *    This must be invoked on any composite value that is to be inserted into
+  *    a tuple, array, range, etc.  Doing this preserves the invariant that
+  *    toasting goes only one level deep in a tuple.
+  * ----------
+  */
+ extern Datum toast_flatten_tuple_datum(Datum value, TupleDesc tupleDesc);
+
+ /* ----------
   * toast_flatten_tuple_attribute -
   *
   *    If a Datum is of composite type, "flatten" it to contain no toasted fields.
!  *    This is a convenience routine for doing toast_flatten_tuple_datum() on
!  *    tuple fields.
   * ----------
   */
! extern Datum toast_flatten_tuple_attribute(Datum value, Form_pg_attribute att);

  /* ----------
   * toast_compress_datum -
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c94e8a..7309022 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct ArrayRefExprState
*** 624,629 ****
--- 624,631 ----
      int16        refelemlength;    /* typlen of the array element type */
      bool        refelembyval;    /* is the element type pass-by-value? */
      char        refelemalign;    /* typalign of the element type */
+     bool        refiscomposite; /* is the element type composite? */
+     TupleDesc    refelemtupdesc; /* may be set if refiscomposite */
  } ArrayRefExprState;

  /* ----------------
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 9bbfaae..63307c1 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** typedef struct ArrayBuildState
*** 88,93 ****
--- 88,96 ----
      int16        typlen;            /* needed info about datatype */
      bool        typbyval;
      char        typalign;
+     bool        typiscomposite;
+     /* use struct pointer to avoid including tupdesc.h here */
+     struct tupleDesc *element_tupdesc;    /* may be set if typiscomposite */
  } ArrayBuildState;

  /*
*************** extern Datum array_replace(PG_FUNCTION_A
*** 218,223 ****
--- 221,230 ----
  extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
            int arraytyplen, int elmlen, bool elmbyval, char elmalign,
            bool *isNull);
+ extern ArrayType *array_set_element(ArrayType *array, int nSubscripts, int *indx,
+                   Datum dataValue, bool isNull,
+                   int arraytyplen, int elmlen, bool elmbyval, char elmalign,
+                   bool elmiscomposite, struct tupleDesc **element_tupdesc);
  extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
            Datum dataValue, bool isNull,
            int arraytyplen, int elmlen, bool elmbyval, char elmalign);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index f46460a..30fb0a0 100644
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
*************** extern bool get_typbyval(Oid typid);
*** 109,114 ****
--- 109,116 ----
  extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval);
  extern void get_typlenbyvalalign(Oid typid, int16 *typlen, bool *typbyval,
                       char *typalign);
+ extern void get_typlenbyvalaligncomp(Oid typid, int16 *typlen, bool *typbyval,
+                          char *typalign, bool *typiscomposite);
  extern Oid    getTypeIOParam(HeapTuple typeTuple);
  extern void get_type_io_data(Oid typid,
                   IOFuncSelector which_func,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 3749fac..27d6b65 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4246,4251 ****
--- 4246,4252 ----
                  ArrayType  *oldarrayval;
                  ArrayType  *newarrayval;
                  SPITupleTable *save_eval_tuptable;
+                 TupleDesc    elemtyptupdesc = NULL;
                  MemoryContext oldcontext;

                  /*
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4297,4302 ****
--- 4298,4304 ----
                      int16        elemtyplen;
                      bool        elemtypbyval;
                      char        elemtypalign;
+                     bool        elemtypiscomposite;

                      /* If target is domain over array, reduce to base type */
                      arraytypoid = getBaseTypeAndTypmod(parenttypoid,
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4312,4321 ****
                      /* Collect needed data about the types */
                      arraytyplen = get_typlen(arraytypoid);

!                     get_typlenbyvalalign(elemtypoid,
!                                          &elemtyplen,
!                                          &elemtypbyval,
!                                          &elemtypalign);

                      /* Now safe to update the cached data */
                      arrayelem->parenttypoid = parenttypoid;
--- 4314,4324 ----
                      /* Collect needed data about the types */
                      arraytyplen = get_typlen(arraytypoid);

!                     get_typlenbyvalaligncomp(elemtypoid,
!                                              &elemtyplen,
!                                              &elemtypbyval,
!                                              &elemtypalign,
!                                              &elemtypiscomposite);

                      /* Now safe to update the cached data */
                      arrayelem->parenttypoid = parenttypoid;
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4327,4332 ****
--- 4330,4336 ----
                      arrayelem->elemtyplen = elemtyplen;
                      arrayelem->elemtypbyval = elemtypbyval;
                      arrayelem->elemtypalign = elemtypalign;
+                     arrayelem->elemtypiscomposite = elemtypiscomposite;
                  }

                  /*
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4395,4409 ****
                  /*
                   * Build the modified array value.
                   */
!                 newarrayval = array_set(oldarrayval,
!                                         nsubscripts,
!                                         subscriptvals,
!                                         coerced_value,
!                                         *isNull,
!                                         arrayelem->arraytyplen,
!                                         arrayelem->elemtyplen,
!                                         arrayelem->elemtypbyval,
!                                         arrayelem->elemtypalign);

                  MemoryContextSwitchTo(oldcontext);

--- 4399,4423 ----
                  /*
                   * Build the modified array value.
                   */
!                 newarrayval = array_set_element(oldarrayval,
!                                                 nsubscripts,
!                                                 subscriptvals,
!                                                 coerced_value,
!                                                 *isNull,
!                                                 arrayelem->arraytyplen,
!                                                 arrayelem->elemtyplen,
!                                                 arrayelem->elemtypbyval,
!                                                 arrayelem->elemtypalign,
!                                                 arrayelem->elemtypiscomposite,
!                                                 &elemtyptupdesc);
!
!                 /*
!                  * Unfortunately, we have no easy way to cache element type
!                  * tupledescs across calls, because there's no clear place
!                  * to release them again.  Perhaps this can be improved later.
!                  */
!                 if (elemtyptupdesc)
!                     ReleaseTupleDesc(elemtyptupdesc);

                  MemoryContextSwitchTo(oldcontext);

diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b4d1498..fc28e07 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct
*** 321,326 ****
--- 321,327 ----
      int16        elemtyplen;        /* typlen of element type */
      bool        elemtypbyval;    /* element type is pass-by-value? */
      char        elemtypalign;    /* typalign of element type */
+     bool        elemtypiscomposite;    /* element type is composite? */
  } PLpgSQL_arrayelem;



pgsql-hackers by date:

Previous
From: Christian Ullrich
Date:
Subject: Re: PostgreSQL in Windows console and Ctrl-C
Next
From: Stephen Frost
Date:
Subject: Re: DISCARD ALL (Again)