]> granicus.if.org Git - postgresql/blobdiff - src/pl/plperl/plperl.c
Fix typo in comment.
[postgresql] / src / pl / plperl / plperl.c
index fbcafe4842e5e69709cb27dae50eb0c522d73f79..da1b8780d3f8d749264a6c4dfce63c6ecc9ef3bb 100644 (file)
@@ -4,7 +4,7 @@
  * IDENTIFICATION
  *
  *       This software is copyrighted by Mark Hollomon
- *       but is shameless cribbed from pltcl.c by Jan Wieck.
+ *       but is shamelessly cribbed from pltcl.c by Jan Wieck.
  *
  *       The author hereby grants permission  to  use,  copy,  modify,
  *       distribute,  and      license this software and its documentation
@@ -33,7 +33,7 @@
  *       ENHANCEMENTS, OR MODIFICATIONS.
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.88 2005/08/12 21:09:34 momjian Exp $
+ *       $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.103 2006/02/28 23:38:13 neilc Exp $
  *
  **********************************************************************/
 
@@ -45,6 +45,7 @@
 #include <ctype.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <locale.h>
 
 /* postgreSQL stuff */
 #include "commands/trigger.h"
 #include "miscadmin.h"
 #include "mb/pg_wchar.h"
 
-/* perl stuff */
-#include "EXTERN.h"
-#include "perl.h"
-#include "XSUB.h"
-#include "ppport.h"
-#include "spi_internal.h"
-
-/* just in case these symbols aren't provided */
-#ifndef pTHX_
-#define pTHX_
-#define pTHX void
-#endif
+/* define this before the perl headers get a chance to mangle DLLIMPORT */
+extern DLLIMPORT bool check_function_bodies;
 
+/* perl stuff */
+#include "plperl.h"
 
 /**********************************************************************
  * The information we cache about loaded procedures
@@ -82,9 +75,9 @@ typedef struct plperl_proc_desc
        bool            lanpltrusted;
        bool            fn_retistuple;  /* true, if function returns tuple */
        bool            fn_retisset;    /* true, if function returns set */
-       bool        fn_retisarray;  /* true if function returns array */
+       bool            fn_retisarray;  /* true if function returns array */
        Oid                     result_oid;             /* Oid of result type */
-       FmgrInfo        result_in_func; /* I/O function and arg for result type */
+       FmgrInfo        result_in_func; /* I/O function and arg for result type */
        Oid                     result_typioparam;
        int                     nargs;
        FmgrInfo        arg_out_func[FUNC_MAX_ARGS];
@@ -92,22 +85,33 @@ typedef struct plperl_proc_desc
        SV                 *reference;
 } plperl_proc_desc;
 
+/*
+ * The information we cache for the duration of a single call to a
+ * function.
+ */
+typedef struct plperl_call_data
+{
+       plperl_proc_desc *prodesc;
+       FunctionCallInfo  fcinfo;
+       Tuplestorestate  *tuple_store;
+       TupleDesc                 ret_tdesc;
+       AttInMetadata    *attinmeta;
+       MemoryContext     tmp_cxt;
+} plperl_call_data;
+
 
 /**********************************************************************
  * Global data
  **********************************************************************/
-static int     plperl_firstcall = 1;
+static bool plperl_firstcall = true;
 static bool plperl_safe_init_done = false;
 static PerlInterpreter *plperl_interp = NULL;
 static HV  *plperl_proc_hash = NULL;
 
 static bool plperl_use_strict = false;
 
-/* these are saved and restored by plperl_call_handler */
-static plperl_proc_desc *plperl_current_prodesc = NULL;
-static FunctionCallInfo plperl_current_caller_info;
-static Tuplestorestate *plperl_current_tuple_store;
-static TupleDesc plperl_current_tuple_desc;
+/* this is saved and restored by plperl_call_handler */
+static plperl_call_data *current_call_data = NULL;
 
 /**********************************************************************
  * Forward declarations
@@ -119,9 +123,6 @@ Datum               plperl_call_handler(PG_FUNCTION_ARGS);
 Datum          plperl_validator(PG_FUNCTION_ARGS);
 void           plperl_init(void);
 
-HV                *plperl_spi_exec(char *query, int limit);
-SV                *plperl_spi_query(char *);
-
 static Datum plperl_func_handler(PG_FUNCTION_ARGS);
 
 static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
@@ -131,8 +132,6 @@ static SV  *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
 static void plperl_init_shared_libs(pTHX);
 static HV  *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
 
-void plperl_return_next(SV *);
-
 /*
  * This routine is a crock, and so is everyplace that calls it.  The problem
  * is that the cached form of plperl functions/queries is allocated permanently
@@ -160,17 +159,17 @@ plperl_init(void)
                return;
 
        DefineCustomBoolVariable(
-               "plperl.use_strict",
-               "If true, will compile trusted and untrusted perl code in strict mode",
-               NULL,
-               &plperl_use_strict,
-               PGC_USERSET,
-               NULL, NULL);
+                                                        "plperl.use_strict",
+         "If true, will compile trusted and untrusted perl code in strict mode",
+                                                        NULL,
+                                                        &plperl_use_strict,
+                                                        PGC_USERSET,
+                                                        NULL, NULL);
 
        EmitWarningsOnPlaceholders("plperl");
 
        plperl_init_interp();
-       plperl_firstcall = 0;
+       plperl_firstcall = false;
 }
 
 
@@ -185,104 +184,187 @@ plperl_init_all(void)
        /* We don't need to do anything yet when a new backend starts. */
 }
 
+/* Each of these macros must represent a single string literal */
+
+#define PERLBOOT \
+       "SPI::bootstrap(); use vars qw(%_SHARED);" \
+       "sub ::plperl_warn { my $msg = shift; " \
+       "       $msg =~ s/\\(eval \\d+\\) //g; &elog(&NOTICE, $msg); } " \
+       "$SIG{__WARN__} = \\&::plperl_warn; " \
+       "sub ::plperl_die { my $msg = shift; " \
+       "       $msg =~ s/\\(eval \\d+\\) //g; die $msg; } " \
+       "$SIG{__DIE__} = \\&::plperl_die; " \
+       "sub ::mkunsafefunc {" \
+       "      my $ret = eval(qq[ sub { $_[0] $_[1] } ]); " \
+       "      $@ =~ s/\\(eval \\d+\\) //g if $@; return $ret; }" \
+       "use strict; " \
+       "sub ::mk_strict_unsafefunc {" \
+       "      my $ret = eval(qq[ sub { use strict; $_[0] $_[1] } ]); " \
+       "      $@ =~ s/\\(eval \\d+\\) //g if $@; return $ret; } " \
+       "sub ::_plperl_to_pg_array {" \
+       "  my $arg = shift; ref $arg eq 'ARRAY' || return $arg; " \
+       "  my $res = ''; my $first = 1; " \
+       "  foreach my $elem (@$arg) " \
+       "  { " \
+       "    $res .= ', ' unless $first; $first = undef; " \
+       "    if (ref $elem) " \
+       "    { " \
+       "      $res .= _plperl_to_pg_array($elem); " \
+       "    } " \
+       "    elsif (defined($elem)) " \
+       "    { " \
+       "      my $str = qq($elem); " \
+       "      $str =~ s/([\"\\\\])/\\\\$1/g; " \
+       "      $res .= qq(\"$str\"); " \
+       "    } " \
+       "    else " \
+       "    { "\
+       "      $res .= 'NULL' ; " \
+       "    } "\
+       "  } " \
+       "  return qq({$res}); " \
+       "} "
+
+#define SAFE_MODULE \
+       "require Safe; $Safe::VERSION"
+
+#define SAFE_OK \
+       "use vars qw($PLContainer); $PLContainer = new Safe('PLPerl');" \
+       "$PLContainer->permit_only(':default');" \
+       "$PLContainer->permit(qw[:base_math !:base_io sort time]);" \
+       "$PLContainer->share(qw[&elog &spi_exec_query &return_next " \
+       "&spi_query &spi_fetchrow " \
+       "&_plperl_to_pg_array " \
+       "&DEBUG &LOG &INFO &NOTICE &WARNING &ERROR %_SHARED ]);" \
+       "sub ::mksafefunc {" \
+       "      my $ret = $PLContainer->reval(qq[sub { $_[0] $_[1] }]); " \
+       "      $@ =~ s/\\(eval \\d+\\) //g if $@; return $ret; }" \
+       "$PLContainer->permit('require'); $PLContainer->reval('use strict;');" \
+       "$PLContainer->deny('require');" \
+       "sub ::mk_strict_safefunc {" \
+       "      my $ret = $PLContainer->reval(qq[sub { BEGIN { strict->import(); } $_[0] $_[1] }]); " \
+       "      $@ =~ s/\\(eval \\d+\\) //g if $@; return $ret; }"
+
+#define SAFE_BAD \
+       "use vars qw($PLContainer); $PLContainer = new Safe('PLPerl');" \
+       "$PLContainer->permit_only(':default');" \
+       "$PLContainer->share(qw[&elog &ERROR ]);" \
+       "sub ::mksafefunc { return $PLContainer->reval(qq[sub { " \
+       "      elog(ERROR,'trusted Perl functions disabled - " \
+       "      please upgrade Perl Safe module to version 2.09 or later');}]); }" \
+       "sub ::mk_strict_safefunc { return $PLContainer->reval(qq[sub { " \
+       "      elog(ERROR,'trusted Perl functions disabled - " \
+       "      please upgrade Perl Safe module to version 2.09 or later');}]); }"
+
 
 static void
 plperl_init_interp(void)
 {
-       static char        *loose_embedding[3] = {
-               "", "-e",
-               /* all one string follows (no commas please) */
-               "SPI::bootstrap(); use vars qw(%_SHARED);"
-               "sub ::plperl_warn { my $msg = shift; &elog(&NOTICE, $msg); } "
-               "$SIG{__WARN__} = \\&::plperl_warn; "
-               "sub ::mkunsafefunc {return eval(qq[ sub { $_[0] $_[1] } ]); }"
-               "sub ::_plperl_to_pg_array"
-               "{"
-               "  my $arg = shift; ref $arg eq 'ARRAY' || return $arg; "
-               "  my $res = ''; my $first = 1; "
-               "  foreach my $elem (@$arg) "
-               "  { "
-               "    $res .= ', ' unless $first; $first = undef; "
-               "    if (ref $elem) "
-               "    { "
-               "      $res .= _plperl_to_pg_array($elem); "
-               "    } "
-               "    else "
-               "    { "
-               "      my $str = qq($elem); "
-               "      $str =~ s/([\"\\\\])/\\\\$1/g; "
-               "      $res .= qq(\"$str\"); "
-               "    } "
-               "  } "
-               "  return qq({$res}); "
-               "} "
+       static char *embedding[3] = {
+               "", "-e", PERLBOOT
        };
 
+#ifdef WIN32
 
-       static char        *strict_embedding[3] = {
-               "", "-e",
-               /* all one string follows (no commas please) */
-               "SPI::bootstrap(); use vars qw(%_SHARED);"
-               "sub ::plperl_warn { my $msg = shift; &elog(&NOTICE, $msg); } "
-               "$SIG{__WARN__} = \\&::plperl_warn; "
-               "sub ::mkunsafefunc {return eval("
-               "qq[ sub { use strict; $_[0] $_[1] } ]); }"
-       };
+       /* 
+        * The perl library on startup does horrible things like call
+        * setlocale(LC_ALL,""). We have protected against that on most
+        * platforms by setting the environment appropriately. However, on
+        * Windows, setlocale() does not consult the environment, so we need
+        * to save the existing locale settings before perl has a chance to 
+        * mangle them and restore them after its dirty deeds are done.
+        *
+        * MSDN ref:
+        * http://msdn.microsoft.com/library/en-us/vclib/html/_crt_locale.asp
+        *
+        * It appears that we only need to do this on interpreter startup, and
+        * subsequent calls to the interpreter don't mess with the locale
+        * settings.
+        *
+        * We restore them using Perl's POSIX::setlocale() function so that
+        * Perl doesn't have a different idea of the locale from Postgres.
+        *
+        */
+
+       char *loc;
+       char *save_collate, *save_ctype, *save_monetary, *save_numeric, *save_time;
+       char buf[1024];
+
+       loc = setlocale(LC_COLLATE,NULL);
+       save_collate = loc ? pstrdup(loc) : NULL;
+       loc = setlocale(LC_CTYPE,NULL);
+       save_ctype = loc ? pstrdup(loc) : NULL;
+       loc = setlocale(LC_MONETARY,NULL);
+       save_monetary = loc ? pstrdup(loc) : NULL;
+       loc = setlocale(LC_NUMERIC,NULL);
+       save_numeric = loc ? pstrdup(loc) : NULL;
+       loc = setlocale(LC_TIME,NULL);
+       save_time = loc ? pstrdup(loc) : NULL;
+
+#endif
 
        plperl_interp = perl_alloc();
        if (!plperl_interp)
                elog(ERROR, "could not allocate Perl interpreter");
 
        perl_construct(plperl_interp);
-       perl_parse(plperl_interp, plperl_init_shared_libs, 3 ,
-                          (plperl_use_strict ? strict_embedding : loose_embedding), NULL);
+       perl_parse(plperl_interp, plperl_init_shared_libs, 3, embedding, NULL);
        perl_run(plperl_interp);
 
        plperl_proc_hash = newHV();
+
+#ifdef WIN32
+
+       eval_pv("use POSIX qw(locale_h);", TRUE); /* croak on failure */
+
+       if (save_collate != NULL)
+       {
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_COLLATE",save_collate);
+               eval_pv(buf,TRUE);
+               pfree(save_collate);
+       }
+       if (save_ctype != NULL)
+       {
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_CTYPE",save_ctype);
+               eval_pv(buf,TRUE);
+               pfree(save_ctype);
+       }
+       if (save_monetary != NULL)
+       {
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_MONETARY",save_monetary);
+               eval_pv(buf,TRUE);
+               pfree(save_monetary);
+       }
+       if (save_numeric != NULL)
+       {
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_NUMERIC",save_numeric);
+               eval_pv(buf,TRUE);
+               pfree(save_numeric);
+       }
+       if (save_time != NULL)
+       {
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_TIME",save_time);
+               eval_pv(buf,TRUE);
+               pfree(save_time);
+       }
+
+#endif
+
 }
 
 
 static void
 plperl_safe_init(void)
 {
-       static char *safe_module =
-       "require Safe; $Safe::VERSION";
-
-       static char *common_safe_ok =
-       "use vars qw($PLContainer); $PLContainer = new Safe('PLPerl');"
-       "$PLContainer->permit_only(':default');"
-       "$PLContainer->permit(qw[:base_math !:base_io sort time]);"
-       "$PLContainer->share(qw[&elog &spi_exec_query &return_next "
-       "&spi_query &spi_fetchrow "
-       "&_plperl_to_pg_array "
-       "&DEBUG &LOG &INFO &NOTICE &WARNING &ERROR %_SHARED ]);"
-                          ;
-
-       static char * strict_safe_ok =
-               "$PLContainer->permit('require');$PLContainer->reval('use strict;');"
-               "$PLContainer->deny('require');"
-               "sub ::mksafefunc { return $PLContainer->reval(qq[ "
-               "             sub { BEGIN { strict->import(); } $_[0] $_[1]}]); }"
-               ;
-
-       static char * loose_safe_ok =
-               "sub ::mksafefunc { return $PLContainer->reval(qq[ "
-               "             sub { $_[0] $_[1]}]); }"
-               ;
-
-       static char *safe_bad =
-       "use vars qw($PLContainer); $PLContainer = new Safe('PLPerl');"
-       "$PLContainer->permit_only(':default');"
-       "$PLContainer->share(qw[&elog &ERROR ]);"
-       "sub ::mksafefunc { return $PLContainer->reval(qq[sub { "
-       "elog(ERROR,'trusted Perl functions disabled - "
-       "please upgrade Perl Safe module to version 2.09 or later');}]); }"
-                          ;
-
        SV                 *res;
        double          safe_version;
 
-       res = eval_pv(safe_module, FALSE);      /* TRUE = croak if failure */
+       res = eval_pv(SAFE_MODULE, FALSE);      /* TRUE = croak if failure */
 
        safe_version = SvNV(res);
 
@@ -291,31 +373,29 @@ plperl_safe_init(void)
         * assume that floating-point comparisons are exact, so use a slightly
         * smaller comparison value.
         */
-       if (safe_version < 2.0899 )
+       if (safe_version < 2.0899)
        {
                /* not safe, so disallow all trusted funcs */
-               eval_pv(safe_bad, FALSE);
+               eval_pv(SAFE_BAD, FALSE);
        }
        else
        {
-               eval_pv(common_safe_ok, FALSE);
-               eval_pv((plperl_use_strict ? strict_safe_ok : loose_safe_ok), FALSE);
+               eval_pv(SAFE_OK, FALSE);
        }
 
        plperl_safe_init_done = true;
 }
 
-
 /*
  * Perl likes to put a newline after its error messages; clean up such
  */
 static char *
 strip_trailing_ws(const char *msg)
 {
-       char   *res = pstrdup(msg);
-       int             len = strlen(res);
+       char       *res = pstrdup(msg);
+       int                     len = strlen(res);
 
-       while (len > 0 && isspace((unsigned char) res[len-1]))
+       while (len > 0 && isspace((unsigned char) res[len - 1]))
                res[--len] = '\0';
        return res;
 }
@@ -338,7 +418,7 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
        hv_iterinit(perlhash);
        while ((val = hv_iternextsv(perlhash, &key, &klen)))
        {
-               int     attn = SPI_fnumber(td, key);
+               int                     attn = SPI_fnumber(td, key);
 
                if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
                        ereport(ERROR,
@@ -358,29 +438,30 @@ plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
 /*
  * convert perl array to postgres string representation
  */
-static SV*
+static SV  *
 plperl_convert_to_pg_array(SV *src)
 {
-    SV* rv;
-       int count;
-       dSP ;
+       SV                 *rv;
+       int                     count;
 
-       PUSHMARK(SP) ;
+       dSP;
+
+       PUSHMARK(SP);
        XPUSHs(src);
-       PUTBACK ;
+       PUTBACK;
 
-       count = call_pv("_plperl_to_pg_array", G_SCALAR);
+       count = call_pv("::_plperl_to_pg_array", G_SCALAR);
 
-       SPAGAIN ;
+       SPAGAIN;
 
        if (count != 1)
-               croak("Big trouble\n") ;
+               elog(ERROR, "unexpected _plperl_to_pg_array failure");
 
        rv = POPs;
-                          
-       PUTBACK ;
 
-    return rv;
+       PUTBACK;
+
+       return rv;
 }
 
 
@@ -404,10 +485,10 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
        tupdesc = tdata->tg_relation->rd_att;
 
        relid = DatumGetCString(
-                               DirectFunctionCall1(oidout,
-                                                                       ObjectIdGetDatum(tdata->tg_relation->rd_id)
-                               )
-                       );
+                                                       DirectFunctionCall1(oidout,
+                                                                 ObjectIdGetDatum(tdata->tg_relation->rd_id)
+                                                                                               )
+               );
 
        hv_store(hv, "name", 4, newSVpv(tdata->tg_trigger->tgname, 0), 0);
        hv_store(hv, "relid", 5, newSVpv(relid, 0), 0);
@@ -449,10 +530,11 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
 
        if (tdata->tg_trigger->tgnargs > 0)
        {
-               AV *av = newAV();
-               for (i=0; i < tdata->tg_trigger->tgnargs; i++)
+               AV                 *av = newAV();
+
+               for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
                        av_push(av, newSVpv(tdata->tg_trigger->tgargs[i], 0));
-               hv_store(hv, "args", 4, newRV_noinc((SV *)av), 0);
+               hv_store(hv, "args", 4, newRV_noinc((SV *) av), 0);
        }
 
        hv_store(hv, "relname", 7,
@@ -474,7 +556,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
                level = "UNKNOWN";
        hv_store(hv, "level", 5, newSVpv(level, 0), 0);
 
-       return newRV_noinc((SV*)hv);
+       return newRV_noinc((SV *) hv);
 }
 
 
@@ -535,8 +617,8 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
                                                         &typinput, &typioparam);
                        fmgr_info(typinput, &finfo);
                        modvalues[slotsused] = FunctionCall3(&finfo,
-                                                                                CStringGetDatum(SvPV(val, PL_na)),
-                                                                                ObjectIdGetDatum(typioparam),
+                                                                                  CStringGetDatum(SvPV(val, PL_na)),
+                                                                                                ObjectIdGetDatum(typioparam),
                                                 Int32GetDatum(tupdesc->attrs[attn - 1]->atttypmod));
                        modnulls[slotsused] = ' ';
                }
@@ -575,19 +657,12 @@ PG_FUNCTION_INFO_V1(plperl_call_handler);
 Datum
 plperl_call_handler(PG_FUNCTION_ARGS)
 {
-       Datum retval;
-       plperl_proc_desc *save_prodesc;
-       FunctionCallInfo save_caller_info;
-       Tuplestorestate *save_tuple_store;
-       TupleDesc save_tuple_desc;
+       Datum           retval;
+       plperl_call_data *save_call_data;
 
        plperl_init_all();
 
-       save_prodesc = plperl_current_prodesc;
-       save_caller_info = plperl_current_caller_info;
-       save_tuple_store = plperl_current_tuple_store;
-       save_tuple_desc = plperl_current_tuple_desc;
-
+       save_call_data = current_call_data;
        PG_TRY();
        {
                if (CALLED_AS_TRIGGER(fcinfo))
@@ -597,19 +672,12 @@ plperl_call_handler(PG_FUNCTION_ARGS)
        }
        PG_CATCH();
        {
-               plperl_current_prodesc = save_prodesc;
-               plperl_current_caller_info = save_caller_info;
-               plperl_current_tuple_store = save_tuple_store;
-               plperl_current_tuple_desc = save_tuple_desc;
+               current_call_data = save_call_data;
                PG_RE_THROW();
        }
        PG_END_TRY();
 
-       plperl_current_prodesc = save_prodesc;
-       plperl_current_caller_info = save_caller_info;
-       plperl_current_tuple_store = save_tuple_store;
-       plperl_current_tuple_desc = save_tuple_desc;
-
+       current_call_data = save_call_data;
        return retval;
 }
 
@@ -625,10 +693,13 @@ plperl_validator(PG_FUNCTION_ARGS)
        Oid                     funcoid = PG_GETARG_OID(0);
        HeapTuple       tuple;
        Form_pg_proc proc;
+       char            functyptype;
+       int                     numargs;
+       Oid                *argtypes;
+       char      **argnames;
+       char       *argmodes;
        bool            istrigger = false;
-       plperl_proc_desc *prodesc;
-
-       plperl_init_all();
+       int                     i;
 
        /* Get the new function's pg_proc entry */
        tuple = SearchSysCache(PROCOID,
@@ -638,14 +709,47 @@ plperl_validator(PG_FUNCTION_ARGS)
                elog(ERROR, "cache lookup failed for function %u", funcoid);
        proc = (Form_pg_proc) GETSTRUCT(tuple);
 
-       /* we assume OPAQUE with no arguments means a trigger */
-       if (proc->prorettype == TRIGGEROID ||
-               (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
-               istrigger = true;
+       functyptype = get_typtype(proc->prorettype);
+
+       /* Disallow pseudotype result */
+       /* except for TRIGGER, RECORD, or VOID */
+       if (functyptype == 'p')
+       {
+               /* we assume OPAQUE with no arguments means a trigger */
+               if (proc->prorettype == TRIGGEROID ||
+                       (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+                       istrigger = true;
+               else if (proc->prorettype != RECORDOID &&
+                                proc->prorettype != VOIDOID)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("plperl functions cannot return type %s",
+                                                       format_type_be(proc->prorettype))));
+       }
+
+       /* Disallow pseudotypes in arguments (either IN or OUT) */
+       numargs = get_func_arg_info(tuple,
+                                                               &argtypes, &argnames, &argmodes);
+       for (i = 0; i < numargs; i++)
+       {
+               if (get_typtype(argtypes[i]) == 'p')
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("plperl functions cannot take type %s",
+                                                       format_type_be(argtypes[i]))));
+       }
 
        ReleaseSysCache(tuple);
 
-       prodesc = compile_plperl_function(funcoid, istrigger);
+       /* Postpone body checks if !check_function_bodies */
+       if (check_function_bodies)
+       {
+               plperl_proc_desc *prodesc;
+
+               plperl_init_all();
+
+               prodesc = compile_plperl_function(funcoid, istrigger);
+       }
 
        /* the result of a validator is ignored */
        PG_RETURN_VOID();
@@ -661,6 +765,7 @@ plperl_create_sub(char *s, bool trusted)
        dSP;
        SV                 *subref;
        int                     count;
+       char       *compile_sub;
 
        if (trusted && !plperl_safe_init_done)
        {
@@ -677,11 +782,20 @@ plperl_create_sub(char *s, bool trusted)
 
        /*
         * G_KEEPERR seems to be needed here, else we don't recognize compile
-        * errors properly.  Perhaps it's because there's another level of
-        * eval inside mksafefunc?
+        * errors properly.  Perhaps it's because there's another level of eval
+        * inside mksafefunc?
         */
-       count = perl_call_pv((trusted ? "mksafefunc" : "mkunsafefunc"),
-                                                G_SCALAR | G_EVAL | G_KEEPERR);
+
+       if (trusted && plperl_use_strict)
+               compile_sub = "::mk_strict_safefunc";
+       else if (plperl_use_strict)
+               compile_sub = "::mk_strict_unsafefunc";
+       else if (trusted)
+               compile_sub = "::mksafefunc";
+       else
+               compile_sub = "::mkunsafefunc";
+
+       count = perl_call_pv(compile_sub, G_SCALAR | G_EVAL | G_KEEPERR);
        SPAGAIN;
 
        if (count != 1)
@@ -760,14 +874,14 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
        SV                 *retval;
        int                     i;
        int                     count;
-       SV                      *sv;
+       SV                 *sv;
 
        ENTER;
        SAVETMPS;
 
        PUSHMARK(SP);
 
-       XPUSHs(&PL_sv_undef); /* no trigger data */
+       XPUSHs(&PL_sv_undef);           /* no trigger data */
 
        for (i = 0; i < desc->nargs; i++)
        {
@@ -802,7 +916,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
                                                                                                fcinfo->arg[i]));
                        sv = newSVpv(tmp, 0);
 #if PERL_BCDVERSION >= 0x5006000L
-                       if (GetDatabaseEncoding() == PG_UTF8) SvUTF8_on(sv);
+                       if (GetDatabaseEncoding() == PG_UTF8)
+                               SvUTF8_on(sv);
 #endif
                        XPUSHs(sv_2mortal(sv));
                        pfree(tmp);
@@ -909,28 +1024,33 @@ plperl_func_handler(PG_FUNCTION_ARGS)
        SV                 *perlret;
        Datum           retval;
        ReturnSetInfo *rsi;
-       SV* array_ret = NULL;
+       SV                 *array_ret = NULL;
+
+       /*
+        * Create the call_data beforing 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));
+       current_call_data->fcinfo = fcinfo;
 
        if (SPI_connect() != SPI_OK_CONNECT)
                elog(ERROR, "could not connect to SPI manager");
 
        prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
+       current_call_data->prodesc = prodesc;
 
-       plperl_current_prodesc = prodesc;
-       plperl_current_caller_info = fcinfo;
-       plperl_current_tuple_store = 0;
-       plperl_current_tuple_desc = 0;
-
-       rsi = (ReturnSetInfo *)fcinfo->resultinfo;
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
 
-       if (!rsi || !IsA(rsi, ReturnSetInfo) ||
-               (rsi->allowedModes & SFRM_Materialize) == 0 ||
-               rsi->expectedDesc == NULL)
+       if (prodesc->fn_retisset)
        {
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("set-valued function called in context that "
-                                               "cannot accept a set")));
+               /* Check context before allowing the call to go through */
+               if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+                       (rsi->allowedModes & SFRM_Materialize) == 0 ||
+                       rsi->expectedDesc == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("set-valued function called in context that "
+                                                       "cannot accept a set")));
        }
 
        perlret = plperl_call_perl_func(prodesc, fcinfo);
@@ -944,19 +1064,22 @@ plperl_func_handler(PG_FUNCTION_ARGS)
        if (SPI_finish() != SPI_OK_FINISH)
                elog(ERROR, "SPI_finish() failed");
 
-       if (prodesc->fn_retisset) 
+       if (prodesc->fn_retisset)
        {
-               /* If the Perl function returned an arrayref, we pretend that it
-                * called return_next() for each element of the array, to handle
-                * old SRFs that didn't know about return_next(). Any other sort
-                * of return value is an error. */
+               /*
+                * If the Perl function returned an arrayref, we pretend that it
+                * called return_next() for each element of the array, to handle old
+                * SRFs that didn't know about return_next(). Any other sort of return
+                * value is an error.
+                */
                if (SvTYPE(perlret) == SVt_RV &&
                        SvTYPE(SvRV(perlret)) == SVt_PVAV)
                {
-                       int i = 0;
-                       SV **svp = 0;
-                       AV *rav = (AV *)SvRV(perlret);
-                       while ((svp = av_fetch(rav, i, FALSE)) != NULL) 
+                       int                     i = 0;
+                       SV                **svp = 0;
+                       AV                 *rav = (AV *) SvRV(perlret);
+
+                       while ((svp = av_fetch(rav, i, FALSE)) != NULL)
                        {
                                plperl_return_next(*svp);
                                i++;
@@ -971,12 +1094,12 @@ plperl_func_handler(PG_FUNCTION_ARGS)
                }
 
                rsi->returnMode = SFRM_Materialize;
-               if (plperl_current_tuple_store) 
+               if (current_call_data->tuple_store)
                {
-                       rsi->setResult = plperl_current_tuple_store;
-                       rsi->setDesc = plperl_current_tuple_desc;
+                       rsi->setResult = current_call_data->tuple_store;
+                       rsi->setDesc = current_call_data->ret_tdesc;
                }
-               retval = (Datum)0;
+               retval = (Datum) 0;
        }
        else if (SvTYPE(perlret) == SVt_NULL)
        {
@@ -984,14 +1107,14 @@ plperl_func_handler(PG_FUNCTION_ARGS)
                if (rsi && IsA(rsi, ReturnSetInfo))
                        rsi->isDone = ExprEndResult;
                fcinfo->isnull = true;
-               retval = (Datum)0;
+               retval = (Datum) 0;
        }
        else if (prodesc->fn_retistuple)
        {
                /* Return a perl hash converted to a Datum */
-               TupleDesc td;
+               TupleDesc       td;
                AttInMetadata *attinmeta;
-               HeapTuple tup;
+               HeapTuple       tup;
 
                if (!SvOK(perlret) || SvTYPE(perlret) != SVt_RV ||
                        SvTYPE(SvRV(perlret)) != SVt_PVHV)
@@ -1012,21 +1135,21 @@ plperl_func_handler(PG_FUNCTION_ARGS)
                }
 
                attinmeta = TupleDescGetAttInMetadata(td);
-               tup = plperl_build_tuple_result((HV *)SvRV(perlret), attinmeta);
+               tup = plperl_build_tuple_result((HV *) SvRV(perlret), attinmeta);
                retval = HeapTupleGetDatum(tup);
        }
        else
        {
-        /* Return a perl string converted to a Datum */
-        char *val;
-        if (prodesc->fn_retisarray && SvROK(perlret) &&
+               /* Return a perl string converted to a Datum */
+               char       *val;
+
+               if (prodesc->fn_retisarray && SvROK(perlret) &&
                        SvTYPE(SvRV(perlret)) == SVt_PVAV)
-        {
-            array_ret = plperl_convert_to_pg_array(perlret);
-            SvREFCNT_dec(perlret);
-            perlret = array_ret;
-        }
+               {
+                       array_ret = plperl_convert_to_pg_array(perlret);
+                       SvREFCNT_dec(perlret);
+                       perlret = array_ret;
+               }
 
                val = SvPV(perlret, PL_na);
 
@@ -1037,8 +1160,9 @@ plperl_func_handler(PG_FUNCTION_ARGS)
        }
 
        if (array_ret == NULL)
-         SvREFCNT_dec(perlret);
+               SvREFCNT_dec(perlret);
 
+       current_call_data = NULL;
        return retval;
 }
 
@@ -1052,14 +1176,20 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
        SV                 *svTD;
        HV                 *hvTD;
 
+       /*
+        * Create the call_data beforing 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));
+       current_call_data->fcinfo = fcinfo;
+
        /* Connect to SPI manager */
        if (SPI_connect() != SPI_OK_CONNECT)
                elog(ERROR, "could not connect to SPI manager");
 
        /* Find or compile the function */
        prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
-
-       plperl_current_prodesc = prodesc;
+       current_call_data->prodesc = prodesc;
 
        svTD = plperl_trigger_build_args(fcinfo);
        perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD);
@@ -1086,7 +1216,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
                else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
                        retval = (Datum) trigdata->tg_trigtuple;
                else
-                       retval = (Datum) 0;     /* can this happen? */
+                       retval = (Datum) 0; /* can this happen? */
        }
        else
        {
@@ -1111,7 +1241,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
                        {
                                ereport(WARNING,
                                                (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
-                                                errmsg("ignoring modified tuple in DELETE trigger")));
+                                          errmsg("ignoring modified tuple in DELETE trigger")));
                                trv = NULL;
                        }
                }
@@ -1130,6 +1260,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
        if (perlret)
                SvREFCNT_dec(perlret);
 
+       current_call_data = NULL;
        return retval;
 }
 
@@ -1143,7 +1274,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
        int                     proname_len;
        plperl_proc_desc *prodesc = NULL;
        int                     i;
-       SV                      **svp;
+       SV                **svp;
 
        /* We'll need the pg_proc tuple in any case... */
        procTup = SearchSysCache(PROCOID,
@@ -1154,7 +1285,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
        procStruct = (Form_pg_proc) GETSTRUCT(procTup);
 
        /************************************************************
-        * Build our internal proc name from the functions Oid
+        * Build our internal proc name from the function's Oid
         ************************************************************/
        if (!is_trigger)
                sprintf(internal_proname, "__PLPerl_proc_%u", fn_oid);
@@ -1179,7 +1310,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                 * function's pg_proc entry without changing its OID.
                 ************************************************************/
                uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
-                       prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+                               prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
 
                if (!uptodate)
                {
@@ -1190,7 +1321,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
 
        /************************************************************
         * If we haven't found it in the hashtable, we analyze
-        * the functions arguments and returntype and store
+        * the function's arguments and return type and store
         * the in-/out-functions in the prodesc block and create
         * a new hashtable entry for it.
         *
@@ -1247,7 +1378,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                if (!is_trigger)
                {
                        typeTup = SearchSysCache(TYPEOID,
-                                                               ObjectIdGetDatum(procStruct->prorettype),
+                                                                        ObjectIdGetDatum(procStruct->prorettype),
                                                                         0, 0, 0);
                        if (!HeapTupleIsValid(typeTup))
                        {
@@ -1279,8 +1410,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                                        free(prodesc);
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("plperl functions cannot return type %s",
-                                                               format_type_be(procStruct->prorettype))));
+                                                        errmsg("plperl functions cannot return type %s",
+                                                                       format_type_be(procStruct->prorettype))));
                                }
                        }
 
@@ -1289,8 +1420,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                        prodesc->fn_retistuple = (typeStruct->typtype == 'c' ||
                                                                          procStruct->prorettype == RECORDOID);
 
-                       prodesc->fn_retisarray = 
-                               (typeStruct->typlen == -1 && typeStruct->typelem) ;
+                       prodesc->fn_retisarray =
+                               (typeStruct->typlen == -1 && typeStruct->typelem);
 
                        perm_fmgr_info(typeStruct->typinput, &(prodesc->result_in_func));
                        prodesc->result_typioparam = getTypeIOParam(typeTup);
@@ -1308,7 +1439,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                        for (i = 0; i < prodesc->nargs; i++)
                        {
                                typeTup = SearchSysCache(TYPEOID,
-                                                       ObjectIdGetDatum(procStruct->proargtypes.values[i]),
+                                                ObjectIdGetDatum(procStruct->proargtypes.values[i]),
                                                                                 0, 0, 0);
                                if (!HeapTupleIsValid(typeTup))
                                {
@@ -1326,8 +1457,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                                        free(prodesc);
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                  errmsg("plperl functions cannot take type %s",
-                                                  format_type_be(procStruct->proargtypes.values[i]))));
+                                                        errmsg("plperl functions cannot take type %s",
+                                               format_type_be(procStruct->proargtypes.values[i]))));
                                }
 
                                if (typeStruct->typtype == 'c')
@@ -1360,7 +1491,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                 ************************************************************/
                prodesc->reference = plperl_create_sub(proc_source, prodesc->lanpltrusted);
                pfree(proc_source);
-               if (!prodesc->reference) /* can this happen? */
+               if (!prodesc->reference)        /* can this happen? */
                {
                        free(prodesc->proname);
                        free(prodesc);
@@ -1397,7 +1528,7 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
                Oid                     typoutput;
                bool            typisvarlena;
                int                     namelen;
-               SV                      *sv;
+               SV                 *sv;
 
                if (tupdesc->attrs[i]->attisdropped)
                        continue;
@@ -1406,7 +1537,8 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
                namelen = strlen(attname);
                attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
 
-               if (isnull) {
+               if (isnull)
+               {
                        /* Store (attname => undef) and move on. */
                        hv_store(hv, attname, namelen, newSV(0), 0);
                        continue;
@@ -1439,8 +1571,8 @@ plperl_spi_exec(char *query, int limit)
        HV                 *ret_hv;
 
        /*
-        * Execute the query inside a sub-transaction, so we can cope with
-        * errors sanely
+        * Execute the query inside a sub-transaction, so we can cope with errors
+        * sanely
         */
        MemoryContext oldcontext = CurrentMemoryContext;
        ResourceOwner oldowner = CurrentResourceOwner;
@@ -1453,7 +1585,7 @@ plperl_spi_exec(char *query, int limit)
        {
                int                     spi_rv;
 
-               spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly,
+               spi_rv = SPI_execute(query, current_call_data->prodesc->fn_readonly,
                                                         limit);
                ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed,
                                                                                                 spi_rv);
@@ -1462,9 +1594,10 @@ plperl_spi_exec(char *query, int limit)
                ReleaseCurrentSubTransaction();
                MemoryContextSwitchTo(oldcontext);
                CurrentResourceOwner = oldowner;
+
                /*
-                * AtEOSubXact_SPI() should not have popped any SPI context,
-                * but just in case it did, make sure we remain connected.
+                * AtEOSubXact_SPI() should not have popped any SPI context, but just
+                * in case it did, make sure we remain connected.
                 */
                SPI_restore_connection();
        }
@@ -1483,9 +1616,9 @@ plperl_spi_exec(char *query, int limit)
                CurrentResourceOwner = oldowner;
 
                /*
-                * If AtEOSubXact_SPI() popped any SPI context of the subxact,
-                * it will have left us in a disconnected state.  We need this
-                * hack to return to connected state.
+                * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will
+                * have left us in a disconnected state.  We need this hack to return
+                * to connected state.
                 */
                SPI_restore_connection();
 
@@ -1536,100 +1669,150 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
 }
 
 
+/*
+ * Note: plperl_return_next is called both in Postgres and Perl contexts.
+ * We report any errors in Postgres fashion (via ereport).     If called in
+ * Perl context, it is SPI.xs's responsibility to catch the error and
+ * convert to a Perl error.  We assume (perhaps without adequate justification)
+ * that we need not abort the current transaction if the Perl code traps the
+ * error.
+ */
 void
 plperl_return_next(SV *sv)
 {
-       plperl_proc_desc *prodesc = plperl_current_prodesc;
-       FunctionCallInfo fcinfo = plperl_current_caller_info;
-       ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
-       MemoryContext cxt;
-       HeapTuple tuple;
-       TupleDesc tupdesc;
+       plperl_proc_desc *prodesc;
+       FunctionCallInfo fcinfo;
+       ReturnSetInfo *rsi;
+       MemoryContext old_cxt;
+       HeapTuple       tuple;
 
        if (!sv)
                return;
 
+       prodesc = current_call_data->prodesc;
+       fcinfo = current_call_data->fcinfo;
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
        if (!prodesc->fn_retisset)
-       {
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("cannot use return_next in a non-SETOF function")));
-       }
 
        if (prodesc->fn_retistuple &&
                !(SvOK(sv) && SvTYPE(sv) == SVt_RV && SvTYPE(SvRV(sv)) == SVt_PVHV))
-       {
                ereport(ERROR,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
                                 errmsg("setof-composite-returning Perl function "
                                                "must call return_next with reference to hash")));
-       }
 
-       cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+       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 function call, so memoize some lookups
+                */
+               if (prodesc->fn_retistuple)
+                       (void) get_call_result_type(fcinfo, NULL, &tupdesc);
+               else
+                       tupdesc = rsi->expectedDesc;
 
-       if (!plperl_current_tuple_store)
-               plperl_current_tuple_store = 
+               /*
+                * Make sure the tuple_store and ret_tdesc are sufficiently
+                * long-lived.
+                */
+               old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+               current_call_data->ret_tdesc = CreateTupleDescCopy(tupdesc);
+               current_call_data->tuple_store =
                        tuplestore_begin_heap(true, false, work_mem);
+               if (prodesc->fn_retistuple)
+               {
+                       current_call_data->attinmeta =
+                               TupleDescGetAttInMetadata(current_call_data->ret_tdesc);
+               }
 
-       if (prodesc->fn_retistuple)
+               MemoryContextSwitchTo(old_cxt);
+       }               
+
+       /*
+        * Producing the tuple we want to return requires making plenty of
+        * palloc() allocations that are not cleaned up. Since this
+        * function can be called many times before the current memory
+        * context is reset, we need to do those allocations in a
+        * temporary context.
+        */
+       if (!current_call_data->tmp_cxt)
        {
-               TypeFuncClass rettype;
-               AttInMetadata *attinmeta;
+               current_call_data->tmp_cxt =
+                       AllocSetContextCreate(rsi->econtext->ecxt_per_tuple_memory,
+                                                                 "PL/Perl return_next temporary cxt",
+                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                 ALLOCSET_DEFAULT_MAXSIZE);
+       }
+
+       old_cxt = MemoryContextSwitchTo(current_call_data->tmp_cxt);
 
-               rettype = get_call_result_type(fcinfo, NULL, &tupdesc);
-               tupdesc = CreateTupleDescCopy(tupdesc);
-               attinmeta = TupleDescGetAttInMetadata(tupdesc);
-               tuple = plperl_build_tuple_result((HV *)SvRV(sv), attinmeta);
+       if (prodesc->fn_retistuple)
+       {
+               tuple = plperl_build_tuple_result((HV *) SvRV(sv),
+                                                                                 current_call_data->attinmeta);
        }
        else
        {
-               Datum ret;
-               bool isNull;
-
-               tupdesc = CreateTupleDescCopy(rsi->expectedDesc);
+               Datum           ret = (Datum) 0;
+               bool            isNull = true;
 
                if (SvOK(sv) && SvTYPE(sv) != SVt_NULL)
                {
-                       char *val = SvPV(sv, PL_na);
+                       char       *val = SvPV(sv, PL_na);
+
                        ret = FunctionCall3(&prodesc->result_in_func,
                                                                PointerGetDatum(val),
                                                                ObjectIdGetDatum(prodesc->result_typioparam),
                                                                Int32GetDatum(-1));
                        isNull = false;
                }
-               else {
-                       ret = (Datum)0;
-                       isNull = true;
-               }
 
-               tuple = heap_form_tuple(tupdesc, &ret, &isNull);
+               tuple = heap_form_tuple(current_call_data->ret_tdesc, &ret, &isNull);
        }
 
-       if (!plperl_current_tuple_desc)
-               plperl_current_tuple_desc = tupdesc;
+       /* Make sure to store the tuple in a long-lived memory context */
+       MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+       tuplestore_puttuple(current_call_data->tuple_store, tuple);
+       MemoryContextSwitchTo(old_cxt);
 
-       tuplestore_puttuple(plperl_current_tuple_store, tuple);
-       heap_freetuple(tuple);
-       MemoryContextSwitchTo(cxt);
+       MemoryContextReset(current_call_data->tmp_cxt);
 }
 
 
 SV *
 plperl_spi_query(char *query)
 {
-       SV *cursor;
+       SV                 *cursor;
 
+       /*
+        * Execute the query inside a sub-transaction, so we can cope with errors
+        * sanely
+        */
        MemoryContext oldcontext = CurrentMemoryContext;
        ResourceOwner oldowner = CurrentResourceOwner;
 
        BeginInternalSubTransaction(NULL);
+       /* Want to run inside function's memory context */
        MemoryContextSwitchTo(oldcontext);
 
        PG_TRY();
        {
-               void *plan;
-               Portal portal = NULL;
+               void       *plan;
+               Portal          portal = NULL;
 
+               /* Create a cursor for the query */
                plan = SPI_prepare(query, 0, NULL);
                if (plan)
                        portal = SPI_cursor_open(NULL, plan, NULL, NULL, false);
@@ -1638,25 +1821,42 @@ plperl_spi_query(char *query)
                else
                        cursor = newSV(0);
 
+               /* Commit the inner transaction, return to outer xact context */
                ReleaseCurrentSubTransaction();
                MemoryContextSwitchTo(oldcontext);
                CurrentResourceOwner = oldowner;
+
+               /*
+                * AtEOSubXact_SPI() should not have popped any SPI context, but just
+                * in case it did, make sure we remain connected.
+                */
                SPI_restore_connection();
        }
        PG_CATCH();
        {
                ErrorData  *edata;
 
+               /* Save error info */
                MemoryContextSwitchTo(oldcontext);
                edata = CopyErrorData();
                FlushErrorState();
 
+               /* Abort the inner transaction */
                RollbackAndReleaseCurrentSubTransaction();
                MemoryContextSwitchTo(oldcontext);
                CurrentResourceOwner = oldowner;
 
+               /*
+                * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will
+                * have left us in a disconnected state.  We need this hack to return
+                * to connected state.
+                */
                SPI_restore_connection();
+
+               /* Punt the error to Perl */
                croak("%s", edata->message);
+
+               /* Can't get here, but keep compiler quiet */
                return NULL;
        }
        PG_END_TRY();
@@ -1668,21 +1868,80 @@ plperl_spi_query(char *query)
 SV *
 plperl_spi_fetchrow(char *cursor)
 {
-       SV *row = newSV(0);
-       Portal p = SPI_cursor_find(cursor);
+       SV                 *row;
 
-       if (!p)
-               return row;
+       /*
+        * Execute the FETCH inside a sub-transaction, so we can cope with errors
+        * sanely
+        */
+       MemoryContext oldcontext = CurrentMemoryContext;
+       ResourceOwner oldowner = CurrentResourceOwner;
 
-       SPI_cursor_fetch(p, true, 1);
-       if (SPI_processed == 0) {
-               SPI_cursor_close(p);
-               return row;
+       BeginInternalSubTransaction(NULL);
+       /* Want to run inside function's memory context */
+       MemoryContextSwitchTo(oldcontext);
+
+       PG_TRY();
+       {
+               Portal          p = SPI_cursor_find(cursor);
+
+               if (!p)
+                       row = newSV(0);
+               else
+               {
+                       SPI_cursor_fetch(p, true, 1);
+                       if (SPI_processed == 0)
+                       {
+                               SPI_cursor_close(p);
+                               row = newSV(0);
+                       }
+                       else
+                       {
+                               row = plperl_hash_from_tuple(SPI_tuptable->vals[0],
+                                                                                        SPI_tuptable->tupdesc);
+                       }
+                       SPI_freetuptable(SPI_tuptable);
+               }
+
+               /* Commit the inner transaction, return to outer xact context */
+               ReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
+
+               /*
+                * AtEOSubXact_SPI() should not have popped any SPI context, but just
+                * in case it did, make sure we remain connected.
+                */
+               SPI_restore_connection();
        }
+       PG_CATCH();
+       {
+               ErrorData  *edata;
+
+               /* Save error info */
+               MemoryContextSwitchTo(oldcontext);
+               edata = CopyErrorData();
+               FlushErrorState();
+
+               /* Abort the inner transaction */
+               RollbackAndReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
+
+               /*
+                * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will
+                * have left us in a disconnected state.  We need this hack to return
+                * to connected state.
+                */
+               SPI_restore_connection();
+
+               /* Punt the error to Perl */
+               croak("%s", edata->message);
 
-       row = plperl_hash_from_tuple(SPI_tuptable->vals[0],
-                                                                SPI_tuptable->tupdesc);
-       SPI_freetuptable(SPI_tuptable);
+               /* Can't get here, but keep compiler quiet */
+               return NULL;
+       }
+       PG_END_TRY();
 
        return row;
 }