FunctionCallInfo fcinfo;
    Tuplestorestate *tuple_store;
    TupleDesc   ret_tdesc;
-   AttInMetadata *attinmeta;
    MemoryContext tmp_cxt;
 } plperl_call_data;
 
 static SV  *split_array(plperl_array_info *info, int first, int last, int nest);
 static SV  *make_array_ref(plperl_array_info *info, int first, int last);
 static SV  *get_perl_array_ref(SV *sv);
-static Datum plperl_sv_to_datum(SV *sv, FmgrInfo *func, Oid typid,
-                  Oid typioparam, int32 typmod, bool *isnull);
-static void _sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam);
-static Datum plperl_array_to_datum(SV *src, Oid typid);
-static ArrayBuildState *_array_to_datum(AV *av, int *ndims, int *dims,
-             int cur_depth, ArrayBuildState *astate, Oid typid, Oid atypid);
+static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+                  FunctionCallInfo fcinfo,
+                  FmgrInfo *finfo, Oid typioparam,
+                  bool *isnull);
+static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
+static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
+static ArrayBuildState *array_to_datum_internal(AV *av, ArrayBuildState *astate,
+                       int *ndims, int *dims, int cur_depth,
+                       Oid arraytypid, Oid elemtypid, int32 typmod,
+                       FmgrInfo *finfo, Oid typioparam);
 static Datum plperl_hash_to_datum(SV *src, TupleDesc td);
 
 static void plperl_init_shared_libs(pTHX);
 /* Build a tuple from a hash. */
 
 static HeapTuple
-plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
+plperl_build_tuple_result(HV *perlhash, TupleDesc td)
 {
-   TupleDesc   td = attinmeta->tupdesc;
    Datum      *values;
    bool       *nulls;
    HE         *he;
        SV         *val = HeVAL(he);
        char       *key = hek2cstr(he);
        int         attn = SPI_fnumber(td, key);
-       bool        isnull;
 
        if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
            ereport(ERROR,
                            key)));
 
        values[attn - 1] = plperl_sv_to_datum(val,
-                                             NULL,
                                              td->attrs[attn - 1]->atttypid,
-                                             InvalidOid,
                                              td->attrs[attn - 1]->atttypmod,
-                                             &isnull);
-       nulls[attn - 1] = isnull;
+                                             NULL,
+                                             NULL,
+                                             InvalidOid,
+                                             &nulls[attn - 1]);
 
        pfree(key);
    }
 static Datum
 plperl_hash_to_datum(SV *src, TupleDesc td)
 {
-   AttInMetadata *attinmeta = TupleDescGetAttInMetadata(td);
-   HeapTuple   tup = plperl_build_tuple_result((HV *) SvRV(src), attinmeta);
+   HeapTuple   tup = plperl_build_tuple_result((HV *) SvRV(src), td);
 
    return HeapTupleGetDatum(tup);
 }
 }
 
 /*
- * helper function for plperl_array_to_datum, does the main recursing
+ * helper function for plperl_array_to_datum, recurses for multi-D arrays
  */
 static ArrayBuildState *
-_array_to_datum(AV *av, int *ndims, int *dims, int cur_depth,
-               ArrayBuildState *astate, Oid typid, Oid atypid)
+array_to_datum_internal(AV *av, ArrayBuildState *astate,
+                       int *ndims, int *dims, int cur_depth,
+                       Oid arraytypid, Oid elemtypid, int32 typmod,
+                       FmgrInfo *finfo, Oid typioparam)
 {
-   int         i = 0;
+   int         i;
    int         len = av_len(av) + 1;
 
    for (i = 0; i < len; i++)
        {
            AV         *nav = (AV *) SvRV(sav);
 
+           /* dimensionality checks */
            if (cur_depth + 1 > MAXDIM)
                ereport(ERROR,
                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                         errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
                                cur_depth + 1, MAXDIM)));
 
-           /* size based off the first element */
+           /* set size when at first element in this level, else compare */
            if (i == 0 && *ndims == cur_depth)
            {
                dims[*ndims] = av_len(nav) + 1;
                (*ndims)++;
            }
-           else
-           {
-               if (av_len(nav) + 1 != dims[cur_depth])
-                   ereport(ERROR,
-                           (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-                            errmsg("multidimensional arrays must have array expressions with matching dimensions")));
-           }
-
-           astate = _array_to_datum(nav, ndims, dims, cur_depth + 1, astate,
-                                    typid, atypid);
+           else if (av_len(nav) + 1 != dims[cur_depth])
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                        errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+           /* recurse to fetch elements of this sub-array */
+           astate = array_to_datum_internal(nav, astate,
+                                            ndims, dims, cur_depth + 1,
+                                            arraytypid, elemtypid, typmod,
+                                            finfo, typioparam);
        }
        else
        {
+           Datum       dat;
            bool        isnull;
-           Datum       dat = plperl_sv_to_datum(svp ? *svp : NULL, NULL,
-                                                atypid, 0, -1, &isnull);
 
-           astate = accumArrayResult(astate, dat, isnull, atypid, NULL);
+           /* scalar after some sub-arrays at same level? */
+           if (*ndims != cur_depth)
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                        errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+           dat = plperl_sv_to_datum(svp ? *svp : NULL,
+                                    elemtypid,
+                                    typmod,
+                                    NULL,
+                                    finfo,
+                                    typioparam,
+                                    &isnull);
+
+           astate = accumArrayResult(astate, dat, isnull,
+                                     elemtypid, CurrentMemoryContext);
        }
    }
 
  * convert perl array ref to a datum
  */
 static Datum
-plperl_array_to_datum(SV *src, Oid typid)
+plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
 {
-   ArrayBuildState *astate = NULL;
-   Oid         atypid;
+   ArrayBuildState *astate;
+   Oid         elemtypid;
+   FmgrInfo    finfo;
+   Oid         typioparam;
    int         dims[MAXDIM];
    int         lbs[MAXDIM];
    int         ndims = 1;
    int         i;
 
-   atypid = get_element_type(typid);
-   if (!atypid)
-       atypid = typid;
+   elemtypid = get_element_type(typid);
+   if (!elemtypid)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("cannot convert Perl array to non-array type %s",
+                       format_type_be(typid))));
+
+   _sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
 
    memset(dims, 0, sizeof(dims));
    dims[0] = av_len((AV *) SvRV(src)) + 1;
 
-   astate = _array_to_datum((AV *) SvRV(src), &ndims, dims, 1, astate, typid,
-                            atypid);
+   astate = array_to_datum_internal((AV *) SvRV(src), NULL,
+                                    &ndims, dims, 1,
+                                    typid, elemtypid, typmod,
+                                    &finfo, typioparam);
 
    if (!astate)
-       return PointerGetDatum(construct_empty_array(atypid));
+       return PointerGetDatum(construct_empty_array(elemtypid));
 
    for (i = 0; i < ndims; i++)
        lbs[i] = 1;
 
-   return makeMdArrayResult(astate, ndims, dims, lbs, CurrentMemoryContext, true);
+   return makeMdArrayResult(astate, ndims, dims, lbs,
+                            CurrentMemoryContext, true);
 }
 
+/* Get the information needed to convert data to the specified PG type */
 static void
-_sv_to_datum_finfo(FmgrInfo *fcinfo, Oid typid, Oid *typioparam)
+_sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam)
 {
    Oid         typinput;
 
    /* XXX would be better to cache these lookups */
    getTypeInputInfo(typid,
                     &typinput, typioparam);
-   fmgr_info(typinput, fcinfo);
+   fmgr_info(typinput, finfo);
 }
 
 /*
- * convert a sv to datum
- * fcinfo and typioparam are optional and will be looked-up if needed
+ * convert Perl SV to PG datum of type typid, typmod typmod
+ *
+ * Pass the PL/Perl function's fcinfo when attempting to convert to the
+ * function's result type; otherwise pass NULL.  This is used when we need to
+ * resolve the actual result type of a function returning RECORD.
+ *
+ * finfo and typioparam should be the results of _sv_to_datum_finfo for the
+ * given typid, or NULL/InvalidOid to let this function do the lookups.
+ *
+ * *isnull is an output parameter.
  */
 static Datum
-plperl_sv_to_datum(SV *sv, FmgrInfo *finfo, Oid typid, Oid typioparam,
-                  int32 typmod, bool *isnull)
+plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+                  FunctionCallInfo fcinfo,
+                  FmgrInfo *finfo, Oid typioparam,
+                  bool *isnull)
 {
    FmgrInfo    tmp;
 
    /* we might recurse */
    check_stack_depth();
 
-   if (isnull)
-       *isnull = false;
+   *isnull = false;
 
-   if (!sv || !SvOK(sv))
+   /*
+    * Return NULL if result is undef, or if we're in a function returning
+    * VOID.  In the latter case, we should pay no attention to the last Perl
+    * statement's result, and this is a convenient means to ensure that.
+    */
+   if (!sv || !SvOK(sv) || typid == VOIDOID)
    {
+       /* look up type info if they did not pass it */
        if (!finfo)
        {
-           _sv_to_datum_finfo(&tmp, typid, &typioparam);
+           _sv_to_datum_finfo(typid, &tmp, &typioparam);
            finfo = &tmp;
        }
-       if (isnull)
-           *isnull = true;
+       *isnull = true;
+       /* must call typinput in case it wants to reject NULL */
        return InputFunctionCall(finfo, NULL, typioparam, typmod);
    }
    else if (SvROK(sv))
    {
+       /* handle references */
        SV         *sav = get_perl_array_ref(sv);
 
        if (sav)
        {
-           return plperl_array_to_datum(sav, typid);
+           /* handle an arrayref */
+           return plperl_array_to_datum(sav, typid, typmod);
        }
        else if (SvTYPE(SvRV(sv)) == SVt_PVHV)
        {
-           TupleDesc   td = lookup_rowtype_tupdesc(typid, typmod);
-           Datum       ret = plperl_hash_to_datum(sv, td);
+           /* handle a hashref */
+           Datum       ret;
+           TupleDesc   td;
 
+           if (!type_is_rowtype(typid))
+               ereport(ERROR,
+                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                        errmsg("cannot convert Perl hash to non-composite type %s",
+                               format_type_be(typid))));
+
+           td = lookup_rowtype_tupdesc_noerror(typid, typmod, true);
+           if (td == NULL)
+           {
+               /* Try to look it up based on our result type */
+               if (fcinfo == NULL ||
+                   get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("function returning record called in context "
+                                   "that cannot accept type record")));
+           }
+
+           ret = plperl_hash_to_datum(sv, td);
+
+           /* Release on the result of get_call_result_type is harmless */
            ReleaseTupleDesc(td);
+
            return ret;
        }
 
+       /* Reference, but not reference to hash or array ... */
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
         errmsg("PL/Perl function must return reference to hash or array")));
    }
    else
    {
+       /* handle a string/number */
        Datum       ret;
        char       *str = sv2cstr(sv);
 
+       /* did not pass in any typeinfo? look it up */
        if (!finfo)
        {
-           _sv_to_datum_finfo(&tmp, typid, &typioparam);
+           _sv_to_datum_finfo(typid, &tmp, &typioparam);
            finfo = &tmp;
        }
 
    if (!OidIsValid(typid))
        elog(ERROR, "lookup failed for type %s", fqtypename);
 
-   datum = plperl_sv_to_datum(sv, NULL, typid, 0, -1, &isnull);
+   datum = plperl_sv_to_datum(sv,
+                              typid, -1,
+                              NULL, NULL, InvalidOid,
+                              &isnull);
 
    if (isnull)
        return NULL;
                            key)));
 
        modvalues[slotsused] = plperl_sv_to_datum(val,
-                                                 NULL,
                                          tupdesc->attrs[attn - 1]->atttypid,
-                                                 InvalidOid,
                                         tupdesc->attrs[attn - 1]->atttypmod,
+                                                 NULL,
+                                                 NULL,
+                                                 InvalidOid,
                                                  &isnull);
 
        modnulls[slotsused] = isnull ? 'n' : ' ';
    Datum       retval = 0;
    ReturnSetInfo *rsi;
    ErrorContextCallback pl_error_context;
-   bool        has_retval = false;
 
    /*
-    * Create the call_data beforing connecting to SPI, so that it is not
+    * Create the call_data before connecting to SPI, so that it is not
     * allocated in the SPI memory context
     */
    current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
            rsi->setDesc = current_call_data->ret_tdesc;
        }
        retval = (Datum) 0;
-       has_retval = true;
    }
-   else if (!SvOK(perlret))
-   {
-       /* Return NULL if Perl code returned undef */
-       if (rsi && IsA(rsi, ReturnSetInfo))
-           rsi->isDone = ExprEndResult;
-   }
-   else if (prodesc->fn_retistuple)
-   {
-       /* Return a perl hash converted to a Datum */
-       TupleDesc   td;
-
-       if (!SvOK(perlret) || !SvROK(perlret) ||
-           SvTYPE(SvRV(perlret)) != SVt_PVHV)
-       {
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATATYPE_MISMATCH),
-                    errmsg("composite-returning PL/Perl function "
-                           "must return reference to hash")));
-       }
-
-       /* XXX should cache the attinmeta data instead of recomputing */
-       if (get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
-       {
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("function returning record called in context "
-                           "that cannot accept type record")));
-       }
-
-       retval = plperl_hash_to_datum(perlret, td);
-       has_retval = true;
-   }
-
-   if (!has_retval)
+   else
    {
-       bool        isnull;
-
        retval = plperl_sv_to_datum(perlret,
-                                   &prodesc->result_in_func,
                                    prodesc->result_oid,
-                                   prodesc->result_typioparam, -1, &isnull);
-       fcinfo->isnull = isnull;
-       has_retval = true;
+                                   -1,
+                                   fcinfo,
+                                   &prodesc->result_in_func,
+                                   prodesc->result_typioparam,
+                                   &fcinfo->isnull);
+
+       if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo))
+           rsi->isDone = ExprEndResult;
    }
 
    /* Restore the previous error callback */
    ErrorContextCallback pl_error_context;
 
    /*
-    * Create the call_data beforing connecting to SPI, so that it is not
+    * Create the call_data before connecting to SPI, so that it is not
     * allocated in the SPI memory context
     */
    current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
                (errcode(ERRCODE_SYNTAX_ERROR),
                 errmsg("cannot use return_next in a non-SETOF function")));
 
-   if (prodesc->fn_retistuple &&
-       !(SvOK(sv) && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV))
-       ereport(ERROR,
-               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("SETOF-composite-returning PL/Perl function "
-                       "must call return_next with reference to hash")));
-
    if (!current_call_data->ret_tdesc)
    {
        TupleDesc   tupdesc;
 
        Assert(!current_call_data->tuple_store);
-       Assert(!current_call_data->attinmeta);
 
        /*
         * This is the first call to return_next in the current PL/Perl
        current_call_data->tuple_store =
            tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
                                  false, work_mem);
-       if (prodesc->fn_retistuple)
-       {
-           current_call_data->attinmeta =
-               TupleDescGetAttInMetadata(current_call_data->ret_tdesc);
-       }
 
        MemoryContextSwitchTo(old_cxt);
    }
    if (!current_call_data->tmp_cxt)
    {
        current_call_data->tmp_cxt =
-           AllocSetContextCreate(rsi->econtext->ecxt_per_tuple_memory,
+           AllocSetContextCreate(CurrentMemoryContext,
                                  "PL/Perl return_next temporary cxt",
                                  ALLOCSET_DEFAULT_MINSIZE,
                                  ALLOCSET_DEFAULT_INITSIZE,
    {
        HeapTuple   tuple;
 
+       if (!(SvOK(sv) && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV))
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("SETOF-composite-returning PL/Perl function "
+                           "must call return_next with reference to hash")));
+
        tuple = plperl_build_tuple_result((HV *) SvRV(sv),
-                                         current_call_data->attinmeta);
+                                         current_call_data->ret_tdesc);
        tuplestore_puttuple(current_call_data->tuple_store, tuple);
    }
    else
        bool        isNull;
 
        ret = plperl_sv_to_datum(sv,
-                                &prodesc->result_in_func,
                                 prodesc->result_oid,
+                                -1,
+                                fcinfo,
+                                &prodesc->result_in_func,
                                 prodesc->result_typioparam,
-                                -1, &isNull);
+                                &isNull);
 
        tuplestore_putvalues(current_call_data->tuple_store,
                             current_call_data->ret_tdesc,
            bool        isnull;
 
            argvalues[i] = plperl_sv_to_datum(argv[i],
-                                             &qdesc->arginfuncs[i],
                                              qdesc->argtypes[i],
+                                             -1,
+                                             NULL,
+                                             &qdesc->arginfuncs[i],
                                              qdesc->argtypioparams[i],
-                                             -1, &isnull);
+                                             &isnull);
            nulls[i] = isnull ? 'n' : ' ';
        }
 
            bool        isnull;
 
            argvalues[i] = plperl_sv_to_datum(argv[i],
-                                             &qdesc->arginfuncs[i],
                                              qdesc->argtypes[i],
+                                             -1,
+                                             NULL,
+                                             &qdesc->arginfuncs[i],
                                              qdesc->argtypioparams[i],
-                                             -1, &isnull);
+                                             &isnull);
            nulls[i] = isnull ? 'n' : ' ';
        }