]> granicus.if.org Git - postgresql/blobdiff - src/pl/plperl/plperl.c
Fix typo in comment.
[postgresql] / src / pl / plperl / plperl.c
index c66c4dd3779ae83c7ba9326360e2ce3823bca3f8..da1b8780d3f8d749264a6c4dfce63c6ecc9ef3bb 100644 (file)
@@ -4,7 +4,7 @@
  * IDENTIFICATION
  *
  *       This software is copyrighted by Mark Hollomon
- *      but is shameless cribbed from pltcl.c by Jan Weick.
+ *       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
  *       OBLIGATION   TO       PROVIDE   MAINTENANCE,   SUPPORT,  UPDATES,
  *       ENHANCEMENTS, OR MODIFICATIONS.
  *
+ * IDENTIFICATION
+ *       $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.103 2006/02/28 23:38:13 neilc Exp $
+ *
  **********************************************************************/
 
+#include "postgres.h"
+/* Defined by Perl */
+#undef _
 
 /* system stuff */
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
+#include <ctype.h>
 #include <fcntl.h>
-#include <string.h>
-#include <setjmp.h>
+#include <unistd.h>
+#include <locale.h>
 
 /* postgreSQL stuff */
-#include "executor/spi.h"
 #include "commands/trigger.h"
-#include "utils/elog.h"
-#include "fmgr.h"
-#include "access/heapam.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/typcache.h"
+#include "miscadmin.h"
+#include "mb/pg_wchar.h"
 
-#include "tcop/tcopprot.h"
-#include "utils/syscache.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
+/* define this before the perl headers get a chance to mangle DLLIMPORT */
+extern DLLIMPORT bool check_function_bodies;
 
 /* perl stuff */
-/*
- * Evil Code Alert
- *
- * both posgreSQL and perl try to do 'the right thing'
- * and provide union semun if the platform doesn't define
- * it in a system header.
- * psql uses HAVE_UNION_SEMUN
- * perl uses HAS_UNION_SEMUN
- * together, they cause compile errors.
- * If we need it, the psql headers above will provide it.
- * So we tell perl that we have it.
- */
-#ifndef HAS_UNION_SEMUN
-#define HAS_UNION_SEMUN
-#endif
-#include <EXTERN.h>
-#include <perl.h>
-
+#include "plperl.h"
 
 /**********************************************************************
  * The information we cache about loaded procedures
 typedef struct plperl_proc_desc
 {
        char       *proname;
-       FmgrInfo        result_in_func;
-       Oid                     result_in_elem;
-       int                     result_in_len;
+       TransactionId fn_xmin;
+       CommandId       fn_cmin;
+       bool            fn_readonly;
+       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 */
+       Oid                     result_oid;             /* Oid of 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];
-       Oid                     arg_out_elem[FUNC_MAX_ARGS];
-       int                     arg_out_len[FUNC_MAX_ARGS];
-       int                     arg_is_rel[FUNC_MAX_ARGS];
+       bool            arg_is_rowtype[FUNC_MAX_ARGS];
        SV                 *reference;
-}                      plperl_proc_desc;
-
+} plperl_proc_desc;
 
-/**********************************************************************
- * The information we cache about prepared and saved plans
- **********************************************************************/
-typedef struct plperl_query_desc
+/*
+ * The information we cache for the duration of a single call to a
+ * function.
+ */
+typedef struct plperl_call_data
 {
-       char            qname[20];
-       void       *plan;
-       int                     nargs;
-       Oid                *argtypes;
-       FmgrInfo   *arginfuncs;
-       Oid                *argtypelems;
-       Datum      *argvalues;
-       int                *arglen;
-}                      plperl_query_desc;
+       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 int     plperl_call_level = 0;
-static int     plperl_restart_in_progress = 0;
-static PerlInterpreter *plperl_safe_interp = NULL;
+static bool plperl_firstcall = true;
+static bool plperl_safe_init_done = false;
+static PerlInterpreter *plperl_interp = NULL;
 static HV  *plperl_proc_hash = NULL;
 
-#if REALLYHAVEITONTHEBALL
-static Tcl_HashTable *plperl_query_hash = NULL;
+static bool plperl_use_strict = false;
 
-#endif
+/* this is saved and restored by plperl_call_handler */
+static plperl_call_data *current_call_data = NULL;
 
 /**********************************************************************
  * Forward declarations
  **********************************************************************/
 static void plperl_init_all(void);
-static void plperl_init_safe_interp(void);
+static void plperl_init_interp(void);
 
-Datum plperl_call_handler(FmgrInfo *proinfo,
-                                       FmgrValues *proargs, bool *isNull);
+Datum          plperl_call_handler(PG_FUNCTION_ARGS);
+Datum          plperl_validator(PG_FUNCTION_ARGS);
+void           plperl_init(void);
 
-static Datum plperl_func_handler(FmgrInfo *proinfo,
-                                       FmgrValues *proargs, bool *isNull);
+static Datum plperl_func_handler(PG_FUNCTION_ARGS);
 
-static SV  *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
-static void plperl_init_shared_libs(void);
+static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
+static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
 
-#ifdef REALLYHAVEITONTHEBALL
-static HeapTuple plperl_trigger_handler(FmgrInfo *proinfo);
+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);
+
+/*
+ * 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
+ * (mostly via malloc()) and never released until backend exit.  Subsidiary
+ * data structures such as fmgr info records therefore must live forever
+ * as well.  A better implementation would store all this stuff in a per-
+ * function memory context that could be reclaimed at need.  In the meantime,
+ * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever
+ * it might allocate, and whatever the eventual function might allocate using
+ * fn_mcxt, will live forever too.
+ */
+static void
+perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
+{
+       fmgr_info_cxt(functionId, finfo, TopMemoryContext);
+}
 
-static int plperl_elog(ClientData cdata, Tcl_Interp *interp,
-                       int argc, char *argv[]);
-static int plperl_quote(ClientData cdata, Tcl_Interp *interp,
-                        int argc, char *argv[]);
 
-static int plperl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
-                               int argc, char *argv[]);
-static int plperl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
-                                  int argc, char *argv[]);
-static int plperl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
-                                int argc, char *argv[]);
+/* Perform initialization during postmaster startup. */
 
-static void plperl_set_tuple_values(Tcl_Interp *interp, char *arrayname,
-                                               int tupno, HeapTuple tuple, TupleDesc tupdesc);
+void
+plperl_init(void)
+{
+       if (!plperl_firstcall)
+               return;
 
-#endif
+       DefineCustomBoolVariable(
+                                                        "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 = false;
+}
+
+
+/* Perform initialization during backend startup. */
 
-/**********************************************************************
- * plperl_init_all()           - Initialize all
- **********************************************************************/
 static void
 plperl_init_all(void)
 {
+       if (plperl_firstcall)
+               plperl_init();
 
-       /************************************************************
-        * Do initialization only once
-        ************************************************************/
-       if (!plperl_firstcall)
-               return;
+       /* 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');}]); }"
 
-       /************************************************************
-        * Destroy the existing safe interpreter
-        ************************************************************/
-       if (plperl_safe_interp != NULL)
+
+static void
+plperl_init_interp(void)
+{
+       static char *embedding[3] = {
+               "", "-e", PERLBOOT
+       };
+
+#ifdef WIN32
+
+       /* 
+        * 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, 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)
        {
-               perl_destruct(plperl_safe_interp);
-               perl_free(plperl_safe_interp);
-               plperl_safe_interp = NULL;
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_COLLATE",save_collate);
+               eval_pv(buf,TRUE);
+               pfree(save_collate);
        }
-
-       /************************************************************
-        * Free the proc hash table
-        ************************************************************/
-       if (plperl_proc_hash != NULL)
+       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)
        {
-               hv_undef(plperl_proc_hash);
-               SvREFCNT_dec((SV *) plperl_proc_hash);
-               plperl_proc_hash = NULL;
+               snprintf(buf, sizeof(buf),"setlocale(%s,'%s');",
+                                "LC_TIME",save_time);
+               eval_pv(buf,TRUE);
+               pfree(save_time);
        }
 
-       /************************************************************
-        * Free the prepared query hash table
-        ************************************************************/
+#endif
+
+}
+
+
+static void
+plperl_safe_init(void)
+{
+       SV                 *res;
+       double          safe_version;
+
+       res = eval_pv(SAFE_MODULE, FALSE);      /* TRUE = croak if failure */
+
+       safe_version = SvNV(res);
 
        /*
-        * if (plperl_query_hash != NULL) { }
+        * We actually want to reject safe_version < 2.09, but it's risky to
+        * assume that floating-point comparisons are exact, so use a slightly
+        * smaller comparison value.
         */
+       if (safe_version < 2.0899)
+       {
+               /* not safe, so disallow all trusted funcs */
+               eval_pv(SAFE_BAD, FALSE);
+       }
+       else
+       {
+               eval_pv(SAFE_OK, FALSE);
+       }
 
-       /************************************************************
-        * Now recreate a new safe interpreter
-        ************************************************************/
-       plperl_init_safe_interp();
+       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);
 
-       plperl_firstcall = 0;
-       return;
+       while (len > 0 && isspace((unsigned char) res[len - 1]))
+               res[--len] = '\0';
+       return res;
 }
 
 
-/**********************************************************************
- * plperl_init_safe_interp() - Create the safe Perl interpreter
- **********************************************************************/
-static void
-plperl_init_safe_interp(void)
+/* Build a tuple from a hash. */
+
+static HeapTuple
+plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
+{
+       TupleDesc       td = attinmeta->tupdesc;
+       char      **values;
+       SV                 *val;
+       char       *key;
+       I32                     klen;
+       HeapTuple       tup;
+
+       values = (char **) palloc0(td->natts * sizeof(char *));
+
+       hv_iterinit(perlhash);
+       while ((val = hv_iternextsv(perlhash, &key, &klen)))
+       {
+               int                     attn = SPI_fnumber(td, key);
+
+               if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                        errmsg("Perl hash contains nonexistent column \"%s\"",
+                                                       key)));
+               if (SvOK(val) && SvTYPE(val) != SVt_NULL)
+                       values[attn - 1] = SvPV(val, PL_na);
+       }
+       hv_iterinit(perlhash);
+
+       tup = BuildTupleFromCStrings(attinmeta, values);
+       pfree(values);
+       return tup;
+}
+
+/*
+ * convert perl array to postgres string representation
+ */
+static SV  *
+plperl_convert_to_pg_array(SV *src)
 {
+       SV                 *rv;
+       int                     count;
+
+       dSP;
 
-       char       *embedding[] = {"", "-e", "use DynaLoader; require Safe; SPI::bootstrap()", "0"};
+       PUSHMARK(SP);
+       XPUSHs(src);
+       PUTBACK;
 
-       plperl_safe_interp = perl_alloc();
-       if (!plperl_safe_interp)
-               elog(ERROR, "plperl_init_safe_interp(): could not allocate perl interpreter");
+       count = call_pv("::_plperl_to_pg_array", G_SCALAR);
 
-       perl_construct(plperl_safe_interp);
-       perl_parse(plperl_safe_interp, plperl_init_shared_libs, 3, embedding, NULL);
-       perl_run(plperl_safe_interp);
+       SPAGAIN;
 
+       if (count != 1)
+               elog(ERROR, "unexpected _plperl_to_pg_array failure");
 
+       rv = POPs;
 
-       /************************************************************
-        * Initialize the proc and query hash tables
-        ************************* ***********************************/
-       plperl_proc_hash = newHV();
+       PUTBACK;
 
+       return rv;
 }
 
 
+/* Set up the arguments for a trigger call. */
 
-/**********************************************************************
- * plperl_call_handler         - This is the only visible function
- *                               of the PL interpreter. The PostgreSQL
- *                               function manager and trigger manager
- *                               call this function for execution of
- *                               perl procedures.
- **********************************************************************/
+static SV  *
+plperl_trigger_build_args(FunctionCallInfo fcinfo)
+{
+       TriggerData *tdata;
+       TupleDesc       tupdesc;
+       int                     i;
+       char       *level;
+       char       *event;
+       char       *relid;
+       char       *when;
+       HV                 *hv;
+
+       hv = newHV();
+
+       tdata = (TriggerData *) fcinfo->context;
+       tupdesc = tdata->tg_relation->rd_att;
+
+       relid = DatumGetCString(
+                                                       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);
+
+       if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
+       {
+               event = "INSERT";
+               if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+                       hv_store(hv, "new", 3,
+                                        plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
+                                        0);
+       }
+       else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
+       {
+               event = "DELETE";
+               if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+                       hv_store(hv, "old", 3,
+                                        plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
+                                        0);
+       }
+       else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
+       {
+               event = "UPDATE";
+               if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+               {
+                       hv_store(hv, "old", 3,
+                                        plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
+                                        0);
+                       hv_store(hv, "new", 3,
+                                        plperl_hash_from_tuple(tdata->tg_newtuple, tupdesc),
+                                        0);
+               }
+       }
+       else
+               event = "UNKNOWN";
+
+       hv_store(hv, "event", 5, newSVpv(event, 0), 0);
+       hv_store(hv, "argc", 4, newSViv(tdata->tg_trigger->tgnargs), 0);
+
+       if (tdata->tg_trigger->tgnargs > 0)
+       {
+               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, "relname", 7,
+                        newSVpv(SPI_getrelname(tdata->tg_relation), 0), 0);
+
+       if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
+               when = "BEFORE";
+       else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
+               when = "AFTER";
+       else
+               when = "UNKNOWN";
+       hv_store(hv, "when", 4, newSVpv(when, 0), 0);
+
+       if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+               level = "ROW";
+       else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
+               level = "STATEMENT";
+       else
+               level = "UNKNOWN";
+       hv_store(hv, "level", 5, newSVpv(level, 0), 0);
+
+       return newRV_noinc((SV *) hv);
+}
+
+
+/* Set up the new tuple returned from a trigger. */
+
+static HeapTuple
+plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
+{
+       SV                **svp;
+       HV                 *hvNew;
+       HeapTuple       rtup;
+       SV                 *val;
+       char       *key;
+       I32                     klen;
+       int                     slotsused;
+       int                *modattrs;
+       Datum      *modvalues;
+       char       *modnulls;
+
+       TupleDesc       tupdesc;
+
+       tupdesc = tdata->tg_relation->rd_att;
+
+       svp = hv_fetch(hvTD, "new", 3, FALSE);
+       if (!svp)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("$_TD->{new} does not exist")));
+       if (!SvOK(*svp) || SvTYPE(*svp) != SVt_RV || SvTYPE(SvRV(*svp)) != SVt_PVHV)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("$_TD->{new} is not a hash reference")));
+       hvNew = (HV *) SvRV(*svp);
+
+       modattrs = palloc(tupdesc->natts * sizeof(int));
+       modvalues = palloc(tupdesc->natts * sizeof(Datum));
+       modnulls = palloc(tupdesc->natts * sizeof(char));
+       slotsused = 0;
+
+       hv_iterinit(hvNew);
+       while ((val = hv_iternextsv(hvNew, &key, &klen)))
+       {
+               int                     attn = SPI_fnumber(tupdesc, key);
+
+               if (attn <= 0 || tupdesc->attrs[attn - 1]->attisdropped)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                        errmsg("Perl hash contains nonexistent column \"%s\"",
+                                                       key)));
+               if (SvOK(val) && SvTYPE(val) != SVt_NULL)
+               {
+                       Oid                     typinput;
+                       Oid                     typioparam;
+                       FmgrInfo        finfo;
+
+                       /* XXX would be better to cache these lookups */
+                       getTypeInputInfo(tupdesc->attrs[attn - 1]->atttypid,
+                                                        &typinput, &typioparam);
+                       fmgr_info(typinput, &finfo);
+                       modvalues[slotsused] = FunctionCall3(&finfo,
+                                                                                  CStringGetDatum(SvPV(val, PL_na)),
+                                                                                                ObjectIdGetDatum(typioparam),
+                                                Int32GetDatum(tupdesc->attrs[attn - 1]->atttypmod));
+                       modnulls[slotsused] = ' ';
+               }
+               else
+               {
+                       modvalues[slotsused] = (Datum) 0;
+                       modnulls[slotsused] = 'n';
+               }
+               modattrs[slotsused] = attn;
+               slotsused++;
+       }
+       hv_iterinit(hvNew);
+
+       rtup = SPI_modifytuple(tdata->tg_relation, otup, slotsused,
+                                                  modattrs, modvalues, modnulls);
+
+       pfree(modattrs);
+       pfree(modvalues);
+       pfree(modnulls);
+
+       if (rtup == NULL)
+               elog(ERROR, "SPI_modifytuple failed: %s",
+                        SPI_result_code_string(SPI_result));
+
+       return rtup;
+}
+
+
+/*
+ * This is the only externally-visible part of the plperl call interface.
+ * The Postgres function and trigger managers call it to execute a
+ * perl function.
+ */
+PG_FUNCTION_INFO_V1(plperl_call_handler);
 
-/* keep non-static */
 Datum
-plperl_call_handler(FmgrInfo *proinfo,
-                                       FmgrValues *proargs,
-                                       bool *isNull)
+plperl_call_handler(PG_FUNCTION_ARGS)
 {
        Datum           retval;
+       plperl_call_data *save_call_data;
 
-       /************************************************************
-        * Initialize interpreters on first call
-        ************************************************************/
-       if (plperl_firstcall)
-               plperl_init_all();
+       plperl_init_all();
 
-       /************************************************************
-        * Connect to SPI manager
-        ************************************************************/
-       if (SPI_connect() != SPI_OK_CONNECT)
-               elog(ERROR, "plperl: cannot connect to SPI manager");
-       /************************************************************
-        * Keep track about the nesting of Tcl-SPI-Tcl-... calls
-        ************************************************************/
-       plperl_call_level++;
+       save_call_data = current_call_data;
+       PG_TRY();
+       {
+               if (CALLED_AS_TRIGGER(fcinfo))
+                       retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
+               else
+                       retval = plperl_func_handler(fcinfo);
+       }
+       PG_CATCH();
+       {
+               current_call_data = save_call_data;
+               PG_RE_THROW();
+       }
+       PG_END_TRY();
 
-       /************************************************************
-        * Determine if called as function or trigger and
-        * call appropriate subhandler
-        ************************************************************/
-       if (CurrentTriggerData == NULL)
-               retval = plperl_func_handler(proinfo, proargs, isNull);
-       else
+       current_call_data = save_call_data;
+       return retval;
+}
+
+/*
+ * This is the other externally visible function - it is called when CREATE
+ * FUNCTION is issued to validate the function being created/replaced.
+ */
+PG_FUNCTION_INFO_V1(plperl_validator);
+
+Datum
+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;
+       int                     i;
+
+       /* Get the new function's pg_proc entry */
+       tuple = SearchSysCache(PROCOID,
+                                                  ObjectIdGetDatum(funcoid),
+                                                  0, 0, 0);
+       if (!HeapTupleIsValid(tuple))
+               elog(ERROR, "cache lookup failed for function %u", funcoid);
+       proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+       functyptype = get_typtype(proc->prorettype);
+
+       /* Disallow pseudotype result */
+       /* except for TRIGGER, RECORD, or VOID */
+       if (functyptype == 'p')
        {
-               elog(ERROR, "plperl: can't use perl in triggers yet.");
+               /* 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))));
+       }
 
-               /*
-                * retval = (Datum) plperl_trigger_handler(proinfo);
-                */
-               /* make the compiler happy */
-               retval = (Datum) 0;
+       /* 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]))));
        }
 
-       plperl_call_level--;
+       ReleaseSysCache(tuple);
 
-       return retval;
+       /* 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();
 }
 
 
-/**********************************************************************
- * plperl_create_sub()         - calls the perl interpreter to
- *             create the anonymous subroutine whose text is in the SV.
- *             Returns the SV containing the RV to the closure.
- **********************************************************************/
-static
-SV *
-plperl_create_sub(SV * s)
+/* Uses mksafefunc/mkunsafefunc to create an anonymous sub whose text is
+ * supplied in s, and returns a reference to the closure. */
+
+static SV  *
+plperl_create_sub(char *s, bool trusted)
 {
        dSP;
+       SV                 *subref;
+       int                     count;
+       char       *compile_sub;
 
-       SV                 *subref = NULL;
+       if (trusted && !plperl_safe_init_done)
+       {
+               plperl_safe_init();
+               SPAGAIN;
+       }
 
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
-       perl_eval_sv(s, G_SCALAR | G_EVAL | G_KEEPERR);
+       XPUSHs(sv_2mortal(newSVpv("my $_TD=$_[0]; shift;", 0)));
+       XPUSHs(sv_2mortal(newSVpv(s, 0)));
+       PUTBACK;
+
+       /*
+        * 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?
+        */
+
+       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 (SvTRUE(GvSV(errgv)))
+       if (count != 1)
+       {
+               PUTBACK;
+               FREETMPS;
+               LEAVE;
+               elog(ERROR, "didn't get a return item from mksafefunc");
+       }
+
+       if (SvTRUE(ERRSV))
        {
-               POPs;
+               (void) POPs;
                PUTBACK;
                FREETMPS;
                LEAVE;
-               elog(ERROR, "creation of function failed : %s", SvPV(GvSV(errgv), na));
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("creation of Perl function failed: %s",
+                                               strip_trailing_ws(SvPV(ERRSV, PL_na)))));
        }
 
        /*
@@ -329,7 +824,7 @@ plperl_create_sub(SV * s)
         */
        subref = newSVsv(POPs);
 
-       if (!SvROK(subref))
+       if (!SvROK(subref) || SvTYPE(SvRV(subref)) != SVt_PVCV)
        {
                PUTBACK;
                FREETMPS;
@@ -339,15 +834,17 @@ plperl_create_sub(SV * s)
                 * subref is our responsibility because it is not mortal
                 */
                SvREFCNT_dec(subref);
-               elog(ERROR, "plperl_create_sub: didn't get a code ref");
+               elog(ERROR, "didn't get a code ref");
        }
 
        PUTBACK;
        FREETMPS;
        LEAVE;
+
        return subref;
 }
 
+
 /**********************************************************************
  * plperl_init_shared_libs()           -
  *
@@ -357,66 +854,79 @@ plperl_create_sub(SV * s)
  *
  **********************************************************************/
 
-extern void boot_DynaLoader _((CV * cv));
-extern void boot_Opcode _((CV * cv));
-extern void boot_SPI _((CV * cv));
+EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
+EXTERN_C void boot_SPI(pTHX_ CV *cv);
 
 static void
-plperl_init_shared_libs(void)
+plperl_init_shared_libs(pTHX)
 {
        char       *file = __FILE__;
 
-       newXS("DynaLoader::bootstrap", boot_DynaLoader, file);
-       newXS("Opcode::bootstrap", boot_Opcode, file);
+       newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
        newXS("SPI::bootstrap", boot_SPI, file);
 }
 
-/**********************************************************************
- * plperl_call_perl_func()             - calls a perl function through the RV
- *                     stored in the prodesc structure. massages the input parms properly
- **********************************************************************/
-static
-SV *
-plperl_call_perl_func(plperl_proc_desc * desc, FmgrValues *pargs)
+
+static SV  *
+plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
 {
        dSP;
-
        SV                 *retval;
        int                     i;
        int                     count;
-
+       SV                 *sv;
 
        ENTER;
        SAVETMPS;
 
-       PUSHMARK(sp);
-       for (i = 0; i < desc->nargs; i++)
-       {
-               if (desc->arg_is_rel[i])
-               {
+       PUSHMARK(SP);
 
-                       /*
-                        * plperl_build_tuple_argument better return a mortal SV.
-                        */
-                       SV                 *hashref = plperl_build_tuple_argument(
-                                                         ((TupleTableSlot *) (pargs->data[i]))->val,
-                        ((TupleTableSlot *) (pargs->data[i]))->ttc_tupleDescriptor);
+       XPUSHs(&PL_sv_undef);           /* no trigger data */
 
-                       XPUSHs(hashref);
+       for (i = 0; i < desc->nargs; i++)
+       {
+               if (fcinfo->argnull[i])
+                       XPUSHs(&PL_sv_undef);
+               else if (desc->arg_is_rowtype[i])
+               {
+                       HeapTupleHeader td;
+                       Oid                     tupType;
+                       int32           tupTypmod;
+                       TupleDesc       tupdesc;
+                       HeapTupleData tmptup;
+                       SV                 *hashref;
+
+                       td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
+                       /* Extract rowtype info and find a tupdesc */
+                       tupType = HeapTupleHeaderGetTypeId(td);
+                       tupTypmod = HeapTupleHeaderGetTypMod(td);
+                       tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+                       /* Build a temporary HeapTuple control structure */
+                       tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+                       tmptup.t_data = td;
+
+                       hashref = plperl_hash_from_tuple(&tmptup, tupdesc);
+                       XPUSHs(sv_2mortal(hashref));
                }
                else
                {
-                       char       *tmp = (*fmgr_faddr(&(desc->arg_out_func[i])))
-                       (pargs->data[i],
-                        desc->arg_out_elem[i],
-                        desc->arg_out_len[i]);
-
-                       XPUSHs(sv_2mortal(newSVpv(tmp, 0)));
+                       char       *tmp;
+
+                       tmp = DatumGetCString(FunctionCall1(&(desc->arg_out_func[i]),
+                                                                                               fcinfo->arg[i]));
+                       sv = newSVpv(tmp, 0);
+#if PERL_BCDVERSION >= 0x5006000L
+                       if (GetDatabaseEncoding() == PG_UTF8)
+                               SvUTF8_on(sv);
+#endif
+                       XPUSHs(sv_2mortal(sv));
                        pfree(tmp);
                }
        }
        PUTBACK;
-       count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL | G_KEEPERR);
+
+       /* Do NOT use G_KEEPERR here */
+       count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL);
 
        SPAGAIN;
 
@@ -425,1767 +935,1013 @@ plperl_call_perl_func(plperl_proc_desc * desc, FmgrValues *pargs)
                PUTBACK;
                FREETMPS;
                LEAVE;
-               elog(ERROR, "plperl : didn't get a return item from function");
+               elog(ERROR, "didn't get a return item from function");
        }
 
-       if (SvTRUE(GvSV(errgv)))
+       if (SvTRUE(ERRSV))
        {
-               POPs;
+               (void) POPs;
                PUTBACK;
                FREETMPS;
                LEAVE;
-               elog(ERROR, "plperl : error from function : %s", SvPV(GvSV(errgv), na));
+               /* XXX need to find a way to assign an errcode here */
+               ereport(ERROR,
+                               (errmsg("error from Perl function: %s",
+                                               strip_trailing_ws(SvPV(ERRSV, PL_na)))));
        }
 
        retval = newSVsv(POPs);
 
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       return retval;
+}
+
+
+static SV  *
+plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo,
+                                                         SV *td)
+{
+       dSP;
+       SV                 *retval;
+       Trigger    *tg_trigger;
+       int                     i;
+       int                     count;
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK(sp);
+
+       XPUSHs(td);
+
+       tg_trigger = ((TriggerData *) fcinfo->context)->tg_trigger;
+       for (i = 0; i < tg_trigger->tgnargs; i++)
+               XPUSHs(sv_2mortal(newSVpv(tg_trigger->tgargs[i], 0)));
+       PUTBACK;
+
+       /* Do NOT use G_KEEPERR here */
+       count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+       SPAGAIN;
+
+       if (count != 1)
+       {
+               PUTBACK;
+               FREETMPS;
+               LEAVE;
+               elog(ERROR, "didn't get a return item from trigger function");
+       }
+
+       if (SvTRUE(ERRSV))
+       {
+               (void) POPs;
+               PUTBACK;
+               FREETMPS;
+               LEAVE;
+               /* XXX need to find a way to assign an errcode here */
+               ereport(ERROR,
+                               (errmsg("error from Perl trigger function: %s",
+                                               strip_trailing_ws(SvPV(ERRSV, PL_na)))));
+       }
+
+       retval = newSVsv(POPs);
 
        PUTBACK;
        FREETMPS;
        LEAVE;
 
        return retval;
+}
+
+
+static Datum
+plperl_func_handler(PG_FUNCTION_ARGS)
+{
+       plperl_proc_desc *prodesc;
+       SV                 *perlret;
+       Datum           retval;
+       ReturnSetInfo *rsi;
+       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;
+
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+       if (prodesc->fn_retisset)
+       {
+               /* 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);
+
+       /************************************************************
+        * Disconnect from SPI manager and then create the return
+        * values datum (if the input function does a palloc for it
+        * this must not be allocated in the SPI memory context
+        * because SPI_finish would free it).
+        ************************************************************/
+       if (SPI_finish() != SPI_OK_FINISH)
+               elog(ERROR, "SPI_finish() failed");
+
+       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 (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)
+                       {
+                               plperl_return_next(*svp);
+                               i++;
+                       }
+               }
+               else if (SvTYPE(perlret) != SVt_NULL)
+               {
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("set-returning Perl function must return "
+                                                       "reference to array or use return_next")));
+               }
+
+               rsi->returnMode = SFRM_Materialize;
+               if (current_call_data->tuple_store)
+               {
+                       rsi->setResult = current_call_data->tuple_store;
+                       rsi->setDesc = current_call_data->ret_tdesc;
+               }
+               retval = (Datum) 0;
+       }
+       else if (SvTYPE(perlret) == SVt_NULL)
+       {
+               /* Return NULL if Perl code returned undef */
+               if (rsi && IsA(rsi, ReturnSetInfo))
+                       rsi->isDone = ExprEndResult;
+               fcinfo->isnull = true;
+               retval = (Datum) 0;
+       }
+       else if (prodesc->fn_retistuple)
+       {
+               /* Return a perl hash converted to a Datum */
+               TupleDesc       td;
+               AttInMetadata *attinmeta;
+               HeapTuple       tup;
+
+               if (!SvOK(perlret) || SvTYPE(perlret) != SVt_RV ||
+                       SvTYPE(SvRV(perlret)) != SVt_PVHV)
+               {
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("composite-returning 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")));
+               }
+
+               attinmeta = TupleDescGetAttInMetadata(td);
+               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) &&
+                       SvTYPE(SvRV(perlret)) == SVt_PVAV)
+               {
+                       array_ret = plperl_convert_to_pg_array(perlret);
+                       SvREFCNT_dec(perlret);
+                       perlret = array_ret;
+               }
 
+               val = SvPV(perlret, PL_na);
 
+               retval = FunctionCall3(&prodesc->result_in_func,
+                                                          CStringGetDatum(val),
+                                                          ObjectIdGetDatum(prodesc->result_typioparam),
+                                                          Int32GetDatum(-1));
+       }
+
+       if (array_ret == NULL)
+               SvREFCNT_dec(perlret);
+
+       current_call_data = NULL;
+       return retval;
 }
 
-/**********************************************************************
- * plperl_func_handler()               - Handler for regular function calls
- **********************************************************************/
+
 static Datum
-plperl_func_handler(FmgrInfo *proinfo,
-                                       FmgrValues *proargs,
-                                       bool *isNull)
+plperl_trigger_handler(PG_FUNCTION_ARGS)
 {
-       int                     i;
-       char            internal_proname[512];
-       int                     proname_len;
-       char       *stroid;
        plperl_proc_desc *prodesc;
        SV                 *perlret;
        Datum           retval;
-       sigjmp_buf      save_restart;
+       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);
+       current_call_data->prodesc = prodesc;
+
+       svTD = plperl_trigger_build_args(fcinfo);
+       perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD);
+       hvTD = (HV *) SvRV(svTD);
+
+       /************************************************************
+       * Disconnect from SPI manager and then create the return
+       * values datum (if the input function does a palloc for it
+       * this must not be allocated in the SPI memory context
+       * because SPI_finish would free it).
+       ************************************************************/
+       if (SPI_finish() != SPI_OK_FINISH)
+               elog(ERROR, "SPI_finish() failed");
+
+       if (!(perlret && SvOK(perlret) && SvTYPE(perlret) != SVt_NULL))
+       {
+               /* undef result means go ahead with original tuple */
+               TriggerData *trigdata = ((TriggerData *) fcinfo->context);
+
+               if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+                       retval = (Datum) trigdata->tg_trigtuple;
+               else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+                       retval = (Datum) trigdata->tg_newtuple;
+               else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+                       retval = (Datum) trigdata->tg_trigtuple;
+               else
+                       retval = (Datum) 0; /* can this happen? */
+       }
+       else
+       {
+               HeapTuple       trv;
+               char       *tmp;
+
+               tmp = SvPV(perlret, PL_na);
+
+               if (pg_strcasecmp(tmp, "SKIP") == 0)
+                       trv = NULL;
+               else if (pg_strcasecmp(tmp, "MODIFY") == 0)
+               {
+                       TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+                       if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+                               trv = plperl_modify_tuple(hvTD, trigdata,
+                                                                                 trigdata->tg_trigtuple);
+                       else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+                               trv = plperl_modify_tuple(hvTD, trigdata,
+                                                                                 trigdata->tg_newtuple);
+                       else
+                       {
+                               ereport(WARNING,
+                                               (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+                                          errmsg("ignoring modified tuple in DELETE trigger")));
+                               trv = NULL;
+                       }
+               }
+               else
+               {
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+                                        errmsg("result of Perl trigger function must be undef, "
+                                                       "\"SKIP\" or \"MODIFY\"")));
+                       trv = NULL;
+               }
+               retval = PointerGetDatum(trv);
+       }
+
+       SvREFCNT_dec(svTD);
+       if (perlret)
+               SvREFCNT_dec(perlret);
+
+       current_call_data = NULL;
+       return retval;
+}
+
+
+static plperl_proc_desc *
+compile_plperl_function(Oid fn_oid, bool is_trigger)
+{
+       HeapTuple       procTup;
+       Form_pg_proc procStruct;
+       char            internal_proname[64];
+       int                     proname_len;
+       plperl_proc_desc *prodesc = NULL;
+       int                     i;
+       SV                **svp;
+
+       /* We'll need the pg_proc tuple in any case... */
+       procTup = SearchSysCache(PROCOID,
+                                                        ObjectIdGetDatum(fn_oid),
+                                                        0, 0, 0);
+       if (!HeapTupleIsValid(procTup))
+               elog(ERROR, "cache lookup failed for function %u", fn_oid);
+       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
         ************************************************************/
-       stroid = oidout(proinfo->fn_oid);
-       strcpy(internal_proname, "__PLperl_proc_");
-       strcat(internal_proname, stroid);
-       pfree(stroid);
+       if (!is_trigger)
+               sprintf(internal_proname, "__PLPerl_proc_%u", fn_oid);
+       else
+               sprintf(internal_proname, "__PLPerl_proc_%u_trigger", fn_oid);
+
        proname_len = strlen(internal_proname);
 
        /************************************************************
         * Lookup the internal proc name in the hashtable
         ************************************************************/
-       if (!hv_exists(plperl_proc_hash, internal_proname, proname_len))
+       svp = hv_fetch(plperl_proc_hash, internal_proname, proname_len, FALSE);
+       if (svp)
        {
+               bool            uptodate;
+
+               prodesc = (plperl_proc_desc *) SvIV(*svp);
+
                /************************************************************
-                * If we haven't found it in the hashtable, we analyze
-                * the functions arguments and returntype and store
-                * the in-/out-functions in the prodesc block and create
-                * a new hashtable entry for it.
-                *
-                * Then we load the procedure into the safe interpreter.
+                * If it's present, must check whether it's still up to date.
+                * This is needed because CREATE OR REPLACE FUNCTION can modify the
+                * function's pg_proc entry without changing its OID.
                 ************************************************************/
-               HeapTuple       procTup;
+               uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+                               prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+
+               if (!uptodate)
+               {
+                       /* need we delete old entry? */
+                       prodesc = NULL;
+               }
+       }
+
+       /************************************************************
+        * If we haven't found it in the hashtable, we analyze
+        * 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.
+        *
+        * Then we load the procedure into the Perl interpreter.
+        ************************************************************/
+       if (prodesc == NULL)
+       {
+               HeapTuple       langTup;
                HeapTuple       typeTup;
-               Form_pg_proc procStruct;
+               Form_pg_language langStruct;
                Form_pg_type typeStruct;
-               SV                 *proc_internal_def;
-               char            proc_internal_args[4096];
+               Datum           prosrcdatum;
+               bool            isnull;
                char       *proc_source;
 
                /************************************************************
                 * Allocate a new procedure description block
                 ************************************************************/
                prodesc = (plperl_proc_desc *) malloc(sizeof(plperl_proc_desc));
-               prodesc->proname = malloc(strlen(internal_proname) + 1);
-               strcpy(prodesc->proname, internal_proname);
+               if (prodesc == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("out of memory")));
+               MemSet(prodesc, 0, sizeof(plperl_proc_desc));
+               prodesc->proname = strdup(internal_proname);
+               prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
+               prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+
+               /* Remember if function is STABLE/IMMUTABLE */
+               prodesc->fn_readonly =
+                       (procStruct->provolatile != PROVOLATILE_VOLATILE);
 
                /************************************************************
-                * Lookup the pg_proc tuple by Oid
+                * Lookup the pg_language tuple by Oid
                 ************************************************************/
-               procTup = SearchSysCacheTuple(PROCOID,
-                                                                         ObjectIdGetDatum(proinfo->fn_oid),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(procTup))
+               langTup = SearchSysCache(LANGOID,
+                                                                ObjectIdGetDatum(procStruct->prolang),
+                                                                0, 0, 0);
+               if (!HeapTupleIsValid(langTup))
                {
                        free(prodesc->proname);
                        free(prodesc);
-                       elog(ERROR, "plperl: cache lookup from pg_proc failed");
+                       elog(ERROR, "cache lookup failed for language %u",
+                                procStruct->prolang);
                }
-               procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+               langStruct = (Form_pg_language) GETSTRUCT(langTup);
+               prodesc->lanpltrusted = langStruct->lanpltrusted;
+               ReleaseSysCache(langTup);
 
                /************************************************************
                 * Get the required information for input conversion of the
                 * return value.
                 ************************************************************/
-               typeTup = SearchSysCacheTuple(TYPEOID,
-                                                               ObjectIdGetDatum(procStruct->prorettype),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(typeTup))
+               if (!is_trigger)
                {
-                       free(prodesc->proname);
-                       free(prodesc);
-                       elog(ERROR, "plperl: cache lookup for return type failed");
-               }
-               typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+                       typeTup = SearchSysCache(TYPEOID,
+                                                                        ObjectIdGetDatum(procStruct->prorettype),
+                                                                        0, 0, 0);
+                       if (!HeapTupleIsValid(typeTup))
+                       {
+                               free(prodesc->proname);
+                               free(prodesc);
+                               elog(ERROR, "cache lookup failed for type %u",
+                                        procStruct->prorettype);
+                       }
+                       typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 
-               if (typeStruct->typrelid != InvalidOid)
-               {
-                       free(prodesc->proname);
-                       free(prodesc);
-                       elog(ERROR, "plperl: return types of tuples not supported yet");
-               }
+                       /* Disallow pseudotype result, except VOID or RECORD */
+                       if (typeStruct->typtype == 'p')
+                       {
+                               if (procStruct->prorettype == VOIDOID ||
+                                       procStruct->prorettype == RECORDOID)
+                                        /* okay */ ;
+                               else if (procStruct->prorettype == TRIGGEROID)
+                               {
+                                       free(prodesc->proname);
+                                       free(prodesc);
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("trigger functions may only be called "
+                                                                       "as triggers")));
+                               }
+                               else
+                               {
+                                       free(prodesc->proname);
+                                       free(prodesc);
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("plperl functions cannot return type %s",
+                                                                       format_type_be(procStruct->prorettype))));
+                               }
+                       }
+
+                       prodesc->result_oid = procStruct->prorettype;
+                       prodesc->fn_retisset = procStruct->proretset;
+                       prodesc->fn_retistuple = (typeStruct->typtype == 'c' ||
+                                                                         procStruct->prorettype == RECORDOID);
+
+                       prodesc->fn_retisarray =
+                               (typeStruct->typlen == -1 && typeStruct->typelem);
+
+                       perm_fmgr_info(typeStruct->typinput, &(prodesc->result_in_func));
+                       prodesc->result_typioparam = getTypeIOParam(typeTup);
 
-               fmgr_info(typeStruct->typinput, &(prodesc->result_in_func));
-               prodesc->result_in_elem = (Oid) (typeStruct->typelem);
-               prodesc->result_in_len = typeStruct->typlen;
+                       ReleaseSysCache(typeTup);
+               }
 
                /************************************************************
                 * Get the required information for output conversion
                 * of all procedure arguments
                 ************************************************************/
-               prodesc->nargs = proinfo->fn_nargs;
-               proc_internal_args[0] = '\0';
-               for (i = 0; i < proinfo->fn_nargs; i++)
+               if (!is_trigger)
                {
-                       typeTup = SearchSysCacheTuple(TYPEOID,
-                                                       ObjectIdGetDatum(procStruct->proargtypes[i]),
-                                                                                 0, 0, 0);
-                       if (!HeapTupleIsValid(typeTup))
+                       prodesc->nargs = procStruct->pronargs;
+                       for (i = 0; i < prodesc->nargs; i++)
                        {
-                               free(prodesc->proname);
-                               free(prodesc);
-                               elog(ERROR, "plperl: cache lookup for argument type failed");
-                       }
-                       typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+                               typeTup = SearchSysCache(TYPEOID,
+                                                ObjectIdGetDatum(procStruct->proargtypes.values[i]),
+                                                                                0, 0, 0);
+                               if (!HeapTupleIsValid(typeTup))
+                               {
+                                       free(prodesc->proname);
+                                       free(prodesc);
+                                       elog(ERROR, "cache lookup failed for type %u",
+                                                procStruct->proargtypes.values[i]);
+                               }
+                               typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 
-                       if (typeStruct->typrelid != InvalidOid)
-                               prodesc->arg_is_rel[i] = 1;
-                       else
-                               prodesc->arg_is_rel[i] = 0;
+                               /* Disallow pseudotype argument */
+                               if (typeStruct->typtype == 'p')
+                               {
+                                       free(prodesc->proname);
+                                       free(prodesc);
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("plperl functions cannot take type %s",
+                                               format_type_be(procStruct->proargtypes.values[i]))));
+                               }
 
-                       fmgr_info(typeStruct->typoutput, &(prodesc->arg_out_func[i]));
-                       prodesc->arg_out_elem[i] = (Oid) (typeStruct->typelem);
-                       prodesc->arg_out_len[i] = typeStruct->typlen;
+                               if (typeStruct->typtype == 'c')
+                                       prodesc->arg_is_rowtype[i] = true;
+                               else
+                               {
+                                       prodesc->arg_is_rowtype[i] = false;
+                                       perm_fmgr_info(typeStruct->typoutput,
+                                                                  &(prodesc->arg_out_func[i]));
+                               }
 
+                               ReleaseSysCache(typeTup);
+                       }
                }
 
                /************************************************************
                 * create the text of the anonymous subroutine.
                 * we do not use a named subroutine so that we can call directly
                 * through the reference.
-                *
                 ************************************************************/
-               proc_source = textout(&(procStruct->prosrc));
-
-               /*
-                * the string has been split for readbility. please don't put
-                * commas between them. Hope everyone is ANSI
-                */
-               proc_internal_def = newSVpvf(
-                                                                        "$::x = new Safe;"
-                                                                        "$::x->permit_only(':default');"
-                                  "$::x->share(qw[&elog &DEBUG &NOTICE &NOIND &ERROR]);"
-                                                                        "use strict;"
-                                  "return $::x->reval( q[ sub { %s } ]);", proc_source);
-
-               pfree(proc_source);
+               prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+                                                                         Anum_pg_proc_prosrc, &isnull);
+               if (isnull)
+                       elog(ERROR, "null prosrc");
+               proc_source = DatumGetCString(DirectFunctionCall1(textout,
+                                                                                                                 prosrcdatum));
 
                /************************************************************
                 * Create the procedure in the interpreter
                 ************************************************************/
-               prodesc->reference = plperl_create_sub(proc_internal_def);
-               if (!prodesc->reference)
+               prodesc->reference = plperl_create_sub(proc_source, prodesc->lanpltrusted);
+               pfree(proc_source);
+               if (!prodesc->reference)        /* can this happen? */
                {
                        free(prodesc->proname);
                        free(prodesc);
-                       elog(ERROR, "plperl: cannot create internal procedure %s",
+                       elog(ERROR, "could not create internal procedure \"%s\"",
                                 internal_proname);
                }
 
-               /************************************************************
-                * Add the proc description block to the hashtable
-                ************************************************************/
                hv_store(plperl_proc_hash, internal_proname, proname_len,
                                 newSViv((IV) prodesc), 0);
        }
-       else
-       {
-               /************************************************************
-                * Found the proc description block in the hashtable
-                ************************************************************/
-               prodesc = (plperl_proc_desc *) SvIV(*hv_fetch(plperl_proc_hash,
-                                                                         internal_proname, proname_len, 0));
-       }
 
+       ReleaseSysCache(procTup);
+
+       return prodesc;
+}
+
+
+/* Build a hash from all attributes of a given tuple. */
+
+static SV  *
+plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
+{
+       HV                 *hv;
+       int                     i;
 
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
+       hv = newHV();
 
-       if (sigsetjmp(Warn_restart, 1) != 0)
+       for (i = 0; i < tupdesc->natts; i++)
        {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               if (--plperl_call_level == 0)
-                       plperl_restart_in_progress = 0;
-               siglongjmp(Warn_restart, 1);
-       }
+               Datum           attr;
+               bool            isnull;
+               char       *attname;
+               char       *outputstr;
+               Oid                     typoutput;
+               bool            typisvarlena;
+               int                     namelen;
+               SV                 *sv;
+
+               if (tupdesc->attrs[i]->attisdropped)
+                       continue;
 
+               attname = NameStr(tupdesc->attrs[i]->attname);
+               namelen = strlen(attname);
+               attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
 
-       /************************************************************
-        * Call the Perl function
-        ************************************************************/
-       perlret = plperl_call_perl_func(prodesc, proargs);
+               if (isnull)
+               {
+                       /* Store (attname => undef) and move on. */
+                       hv_store(hv, attname, namelen, newSV(0), 0);
+                       continue;
+               }
 
-       /************************************************************
-        * Disconnect from SPI manager and then create the return
-        * values datum (if the input function does a palloc for it
-        * this must not be allocated in the SPI memory context
-        * because SPI_finish would free it).
-        ************************************************************/
-       if (SPI_finish() != SPI_OK_FINISH)
-               elog(ERROR, "plperl: SPI_finish() failed");
+               /* XXX should have a way to cache these lookups */
 
-       retval = (Datum) (*fmgr_faddr(&prodesc->result_in_func))
-               (SvPV(perlret, na),
-                prodesc->result_in_elem,
-                prodesc->result_in_len);
+               getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
+                                                 &typoutput, &typisvarlena);
 
-       SvREFCNT_dec(perlret);
+               outputstr = DatumGetCString(OidFunctionCall1(typoutput, attr));
 
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       if (plperl_restart_in_progress)
-       {
-               if (--plperl_call_level == 0)
-                       plperl_restart_in_progress = 0;
-               siglongjmp(Warn_restart, 1);
+               sv = newSVpv(outputstr, 0);
+#if PERL_BCDVERSION >= 0x5006000L
+               if (GetDatabaseEncoding() == PG_UTF8)
+                       SvUTF8_on(sv);
+#endif
+               hv_store(hv, attname, namelen, sv, 0);
+
+               pfree(outputstr);
        }
 
-       return retval;
+       return newRV_noinc((SV *) hv);
 }
 
 
-#ifdef REALLYHAVEITONTHEBALL
-/**********************************************************************
- * plperl_trigger_handler() - Handler for trigger calls
- **********************************************************************/
-static HeapTuple
-plperl_trigger_handler(FmgrInfo *proinfo)
+HV *
+plperl_spi_exec(char *query, int limit)
 {
-       TriggerData *trigdata;
-       char            internal_proname[512];
-       char       *stroid;
-       Tcl_HashEntry *hashent;
-       int                     hashnew;
-       plperl_proc_desc *prodesc;
-       TupleDesc       tupdesc;
-       HeapTuple       rettup;
-       Tcl_DString tcl_cmd;
-       Tcl_DString tcl_trigtup;
-       Tcl_DString tcl_newtup;
-       int                     tcl_rc;
-       int                     i;
+       HV                 *ret_hv;
 
-       int                *modattrs;
-       Datum      *modvalues;
-       char       *modnulls;
+       /*
+        * Execute the query inside a sub-transaction, so we can cope with errors
+        * sanely
+        */
+       MemoryContext oldcontext = CurrentMemoryContext;
+       ResourceOwner oldowner = CurrentResourceOwner;
 
-       int                     ret_numvals;
-       char      **ret_values;
+       BeginInternalSubTransaction(NULL);
+       /* Want to run inside function's memory context */
+       MemoryContextSwitchTo(oldcontext);
 
-       sigjmp_buf      save_restart;
+       PG_TRY();
+       {
+               int                     spi_rv;
 
-       /************************************************************
-        * Save the current trigger data local
-        ************************************************************/
-       trigdata = CurrentTriggerData;
-       CurrentTriggerData = NULL;
-
-       /************************************************************
-        * Build our internal proc name from the functions Oid
-        ************************************************************/
-       stroid = oidout(proinfo->fn_oid);
-       strcpy(internal_proname, "__PLTcl_proc_");
-       strcat(internal_proname, stroid);
-       pfree(stroid);
-
-       /************************************************************
-        * Lookup the internal proc name in the hashtable
-        ************************************************************/
-       hashent = Tcl_FindHashEntry(plperl_proc_hash, internal_proname);
-       if (hashent == NULL)
-       {
-               /************************************************************
-                * If we haven't found it in the hashtable,
-                * we load the procedure into the safe interpreter.
-                ************************************************************/
-               Tcl_DString proc_internal_def;
-               Tcl_DString proc_internal_body;
-               HeapTuple       procTup;
-               Form_pg_proc procStruct;
-               char       *proc_source;
-
-               /************************************************************
-                * Allocate a new procedure description block
-                ************************************************************/
-               prodesc = (plperl_proc_desc *) malloc(sizeof(plperl_proc_desc));
-               memset(prodesc, 0, sizeof(plperl_proc_desc));
-               prodesc->proname = malloc(strlen(internal_proname) + 1);
-               strcpy(prodesc->proname, internal_proname);
-
-               /************************************************************
-                * Lookup the pg_proc tuple by Oid
-                ************************************************************/
-               procTup = SearchSysCacheTuple(PROCOID,
-                                                                         ObjectIdGetDatum(proinfo->fn_oid),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(procTup))
-               {
-                       free(prodesc->proname);
-                       free(prodesc);
-                       elog(ERROR, "plperl: cache lookup from pg_proc failed");
-               }
-               procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+               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);
 
-               /************************************************************
-                * Create the tcl command to define the internal
-                * procedure
-                ************************************************************/
-               Tcl_DStringInit(&proc_internal_def);
-               Tcl_DStringInit(&proc_internal_body);
-               Tcl_DStringAppendElement(&proc_internal_def, "proc");
-               Tcl_DStringAppendElement(&proc_internal_def, internal_proname);
-               Tcl_DStringAppendElement(&proc_internal_def,
-                                                                "TG_name TG_relid TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
-
-               /************************************************************
-                * prefix procedure body with
-                * upvar #0 <internal_procname> GD
-                * and with appropriate setting of NEW, OLD,
-                * and the arguments as numerical variables.
-                ************************************************************/
-               Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
-               Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
-               Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
-
-               Tcl_DStringAppend(&proc_internal_body,
-                                                 "array set NEW $__PLTcl_Tup_NEW\n", -1);
-               Tcl_DStringAppend(&proc_internal_body,
-                                                 "array set OLD $__PLTcl_Tup_OLD\n", -1);
-
-               Tcl_DStringAppend(&proc_internal_body,
-                                                 "set i 0\n"
-                                                 "set v 0\n"
-                                                 "foreach v $args {\n"
-                                                 "  incr i\n"
-                                                 "  set $i $v\n"
-                                                 "}\n"
-                                                 "unset i v\n\n", -1);
-
-               proc_source = textout(&(procStruct->prosrc));
-               Tcl_DStringAppend(&proc_internal_body, proc_source, -1);
-               pfree(proc_source);
-               Tcl_DStringAppendElement(&proc_internal_def,
-                                                                Tcl_DStringValue(&proc_internal_body));
-               Tcl_DStringFree(&proc_internal_body);
-
-               /************************************************************
-                * Create the procedure in the safe interpreter
-                ************************************************************/
-               tcl_rc = Tcl_GlobalEval(plperl_safe_interp,
-                                                               Tcl_DStringValue(&proc_internal_def));
-               Tcl_DStringFree(&proc_internal_def);
-               if (tcl_rc != TCL_OK)
-               {
-                       free(prodesc->proname);
-                       free(prodesc);
-                       elog(ERROR, "plperl: cannot create internal procedure %s - %s",
-                                internal_proname, plperl_safe_interp->result);
-               }
+               /* Commit the inner transaction, return to outer xact context */
+               ReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-               /************************************************************
-                * Add the proc description block to the hashtable
-                ************************************************************/
-               hashent = Tcl_CreateHashEntry(plperl_proc_hash,
-                                                                         prodesc->proname, &hashnew);
-               Tcl_SetHashValue(hashent, (ClientData) prodesc);
+               /*
+                * AtEOSubXact_SPI() should not have popped any SPI context, but just
+                * in case it did, make sure we remain connected.
+                */
+               SPI_restore_connection();
        }
-       else
+       PG_CATCH();
        {
-               /************************************************************
-                * Found the proc description block in the hashtable
-                ************************************************************/
-               prodesc = (plperl_proc_desc *) Tcl_GetHashValue(hashent);
-       }
-
-       tupdesc = trigdata->tg_relation->rd_att;
+               ErrorData  *edata;
 
-       /************************************************************
-        * Create the tcl command to call the internal
-        * proc in the safe interpreter
-        ************************************************************/
-       Tcl_DStringInit(&tcl_cmd);
-       Tcl_DStringInit(&tcl_trigtup);
-       Tcl_DStringInit(&tcl_newtup);
+               /* Save error info */
+               MemoryContextSwitchTo(oldcontext);
+               edata = CopyErrorData();
+               FlushErrorState();
 
-       /************************************************************
-        * We call external functions below - care for elog(ERROR)
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               Tcl_DStringFree(&tcl_cmd);
-               Tcl_DStringFree(&tcl_trigtup);
-               Tcl_DStringFree(&tcl_newtup);
-               plperl_restart_in_progress = 1;
-               if (--plperl_call_level == 0)
-                       plperl_restart_in_progress = 0;
-               siglongjmp(Warn_restart, 1);
-       }
+               /* Abort the inner transaction */
+               RollbackAndReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-       /* The procedure name */
-       Tcl_DStringAppendElement(&tcl_cmd, internal_proname);
+               /*
+                * 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();
 
-       /* The trigger name for argument TG_name */
-       Tcl_DStringAppendElement(&tcl_cmd, trigdata->tg_trigger->tgname);
+               /* Punt the error to Perl */
+               croak("%s", edata->message);
 
-       /* The oid of the trigger relation for argument TG_relid */
-       stroid = oidout(trigdata->tg_relation->rd_id);
-       Tcl_DStringAppendElement(&tcl_cmd, stroid);
-       pfree(stroid);
+               /* Can't get here, but keep compiler quiet */
+               return NULL;
+       }
+       PG_END_TRY();
 
-       /* A list of attribute names for argument TG_relatts */
-       Tcl_DStringAppendElement(&tcl_trigtup, "");
-       for (i = 0; i < tupdesc->natts; i++)
-               Tcl_DStringAppendElement(&tcl_trigtup, tupdesc->attrs[i]->attname.data);
-       Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
-       Tcl_DStringFree(&tcl_trigtup);
-       Tcl_DStringInit(&tcl_trigtup);
-
-       /* The when part of the event for TG_when */
-       if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
-               Tcl_DStringAppendElement(&tcl_cmd, "BEFORE");
-       else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
-               Tcl_DStringAppendElement(&tcl_cmd, "AFTER");
-       else
-               Tcl_DStringAppendElement(&tcl_cmd, "UNKNOWN");
+       return ret_hv;
+}
 
-       /* The level part of the event for TG_level */
-       if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-               Tcl_DStringAppendElement(&tcl_cmd, "ROW");
-       else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
-               Tcl_DStringAppendElement(&tcl_cmd, "STATEMENT");
-       else
-               Tcl_DStringAppendElement(&tcl_cmd, "UNKNOWN");
 
-       /* Build the data list for the trigtuple */
-       plperl_build_tuple_argument(trigdata->tg_trigtuple,
-                                                               tupdesc, &tcl_trigtup);
+static HV  *
+plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
+                                                               int status)
+{
+       HV                 *result;
 
-       /*
-        * Now the command part of the event for TG_op and data for NEW and
-        * OLD
-        */
-       if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-       {
-               Tcl_DStringAppendElement(&tcl_cmd, "INSERT");
+       result = newHV();
 
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
-               Tcl_DStringAppendElement(&tcl_cmd, "");
+       hv_store(result, "status", strlen("status"),
+                        newSVpv((char *) SPI_result_code_string(status), 0), 0);
+       hv_store(result, "processed", strlen("processed"),
+                        newSViv(processed), 0);
 
-               rettup = trigdata->tg_trigtuple;
-       }
-       else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+       if (status == SPI_OK_SELECT)
        {
-               Tcl_DStringAppendElement(&tcl_cmd, "DELETE");
+               AV                 *rows;
+               SV                 *row;
+               int                     i;
 
-               Tcl_DStringAppendElement(&tcl_cmd, "");
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
-
-               rettup = trigdata->tg_trigtuple;
+               rows = newAV();
+               for (i = 0; i < processed; i++)
+               {
+                       row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
+                       av_push(rows, row);
+               }
+               hv_store(result, "rows", strlen("rows"),
+                                newRV_noinc((SV *) rows), 0);
        }
-       else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       {
-               Tcl_DStringAppendElement(&tcl_cmd, "UPDATE");
 
-               plperl_build_tuple_argument(trigdata->tg_newtuple,
-                                                                       tupdesc, &tcl_newtup);
+       SPI_freetuptable(tuptable);
 
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_newtup));
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
-
-               rettup = trigdata->tg_newtuple;
-       }
-       else
-       {
-               Tcl_DStringAppendElement(&tcl_cmd, "UNKNOWN");
+       return result;
+}
 
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
-               Tcl_DStringAppendElement(&tcl_cmd, Tcl_DStringValue(&tcl_trigtup));
 
-               rettup = trigdata->tg_trigtuple;
-       }
+/*
+ * 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;
+       FunctionCallInfo fcinfo;
+       ReturnSetInfo *rsi;
+       MemoryContext old_cxt;
+       HeapTuple       tuple;
 
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       Tcl_DStringFree(&tcl_trigtup);
-       Tcl_DStringFree(&tcl_newtup);
+       if (!sv)
+               return;
 
-       /************************************************************
-        * Finally append the arguments from CREATE TRIGGER
-        ************************************************************/
-       for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
-               Tcl_DStringAppendElement(&tcl_cmd, trigdata->tg_trigger->tgargs[i]);
+       prodesc = current_call_data->prodesc;
+       fcinfo = current_call_data->fcinfo;
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
 
-       /************************************************************
-        * Call the Tcl function
-        ************************************************************/
-       tcl_rc = Tcl_GlobalEval(plperl_safe_interp, Tcl_DStringValue(&tcl_cmd));
-       Tcl_DStringFree(&tcl_cmd);
+       if (!prodesc->fn_retisset)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("cannot use return_next in a non-SETOF function")));
 
-       /************************************************************
-        * Check the return code from Tcl and handle
-        * our special restart mechanism to get rid
-        * of all nested call levels on transaction
-        * abort.
-        ************************************************************/
-       if (tcl_rc == TCL_ERROR || plperl_restart_in_progress)
-       {
-               if (!plperl_restart_in_progress)
-               {
-                       plperl_restart_in_progress = 1;
-                       if (--plperl_call_level == 0)
-                               plperl_restart_in_progress = 0;
-                       elog(ERROR, "plperl: %s", plperl_safe_interp->result);
-               }
-               if (--plperl_call_level == 0)
-                       plperl_restart_in_progress = 0;
-               siglongjmp(Warn_restart, 1);
-       }
+       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")));
 
-       switch (tcl_rc)
+       if (!current_call_data->ret_tdesc)
        {
-               case TCL_OK:
-                       break;
+               TupleDesc tupdesc;
 
-               default:
-                       elog(ERROR, "plperl: unsupported TCL return code %d", tcl_rc);
-       }
+               Assert(!current_call_data->tuple_store);
+               Assert(!current_call_data->attinmeta);
 
-       /************************************************************
-        * The return value from the procedure might be one of
-        * the magic strings OK or SKIP or a list from array get
-        ************************************************************/
-       if (SPI_finish() != SPI_OK_FINISH)
-               elog(ERROR, "plperl: SPI_finish() failed");
+               /*
+                * 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 (strcmp(plperl_safe_interp->result, "OK") == 0)
-               return rettup;
-       if (strcmp(plperl_safe_interp->result, "SKIP") == 0)
-       {
-               return (HeapTuple) NULL;;
-       }
+               /*
+                * Make sure the tuple_store and ret_tdesc are sufficiently
+                * long-lived.
+                */
+               old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
 
-       /************************************************************
-        * Convert the result value from the safe interpreter
-        * and setup structures for SPI_modifytuple();
-        ************************************************************/
-       if (Tcl_SplitList(plperl_safe_interp, plperl_safe_interp->result,
-                                         &ret_numvals, &ret_values) != TCL_OK)
-       {
-               elog(NOTICE, "plperl: cannot split return value from trigger");
-               elog(ERROR, "plperl: %s", plperl_safe_interp->result);
-       }
+               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 (ret_numvals % 2 != 0)
-       {
-               ckfree(ret_values);
-               elog(ERROR, "plperl: invalid return list from trigger - must have even # of elements");
-       }
+               MemoryContextSwitchTo(old_cxt);
+       }               
 
-       modattrs = (int *) palloc(tupdesc->natts * sizeof(int));
-       modvalues = (Datum *) palloc(tupdesc->natts * sizeof(Datum));
-       for (i = 0; i < tupdesc->natts; i++)
+       /*
+        * 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)
        {
-               modattrs[i] = i + 1;
-               modvalues[i] = (Datum) NULL;
+               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);
        }
 
-       modnulls = palloc(tupdesc->natts + 1);
-       memset(modnulls, 'n', tupdesc->natts);
-       modnulls[tupdesc->natts] = '\0';
+       old_cxt = MemoryContextSwitchTo(current_call_data->tmp_cxt);
 
-       /************************************************************
-        * Care for possible elog(ERROR)'s below
-        ************************************************************/
-       if (sigsetjmp(Warn_restart, 1) != 0)
+       if (prodesc->fn_retistuple)
        {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               ckfree(ret_values);
-               plperl_restart_in_progress = 1;
-               if (--plperl_call_level == 0)
-                       plperl_restart_in_progress = 0;
-               siglongjmp(Warn_restart, 1);
+               tuple = plperl_build_tuple_result((HV *) SvRV(sv),
+                                                                                 current_call_data->attinmeta);
        }
-
-       i = 0;
-       while (i < ret_numvals)
+       else
        {
-               int                     attnum;
-               HeapTuple       typeTup;
-               Oid                     typinput;
-               Oid                     typelem;
-               FmgrInfo        finfo;
+               Datum           ret = (Datum) 0;
+               bool            isNull = true;
 
-               /************************************************************
-                * Ignore pseudo elements with a dot name
-                ************************************************************/
-               if (*(ret_values[i]) == '.')
+               if (SvOK(sv) && SvTYPE(sv) != SVt_NULL)
                {
-                       i += 2;
-                       continue;
-               }
+                       char       *val = SvPV(sv, PL_na);
 
-               /************************************************************
-                * Get the attribute number
-                ************************************************************/
-               attnum = SPI_fnumber(tupdesc, ret_values[i++]);
-               if (attnum == SPI_ERROR_NOATTRIBUTE)
-                       elog(ERROR, "plperl: invalid attribute '%s'", ret_values[--i]);
-
-               /************************************************************
-                * Lookup the attribute type in the syscache
-                * for the input function
-                ************************************************************/
-               typeTup = SearchSysCacheTuple(TYPEOID,
-                                 ObjectIdGetDatum(tupdesc->attrs[attnum - 1]->atttypid),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(typeTup))
-               {
-                       elog(ERROR, "plperl: Cache lookup for attribute '%s' type %ld failed",
-                                ret_values[--i],
-                                ObjectIdGetDatum(tupdesc->attrs[attnum - 1]->atttypid));
+                       ret = FunctionCall3(&prodesc->result_in_func,
+                                                               PointerGetDatum(val),
+                                                               ObjectIdGetDatum(prodesc->result_typioparam),
+                                                               Int32GetDatum(-1));
+                       isNull = false;
                }
-               typinput = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typinput);
-               typelem = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typelem);
 
-               /************************************************************
-                * Set the attribute to NOT NULL and convert the contents
-                ************************************************************/
-               modnulls[attnum - 1] = ' ';
-               fmgr_info(typinput, &finfo);
-               modvalues[attnum - 1] = (Datum) (*fmgr_faddr(&finfo))
-                       (ret_values[i++],
-                        typelem,
-                        (!VARLENA_FIXED_SIZE(tupdesc->attrs[attnum - 1]))
-                        ? tupdesc->attrs[attnum - 1]->attlen
-                        : tupdesc->attrs[attnum - 1]->atttypmod
-                       );
+               tuple = heap_form_tuple(current_call_data->ret_tdesc, &ret, &isNull);
        }
 
+       /* 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);
 
-       rettup = SPI_modifytuple(trigdata->tg_relation, rettup, tupdesc->natts,
-                                                        modattrs, modvalues, modnulls);
-
-       pfree(modattrs);
-       pfree(modvalues);
-       pfree(modnulls);
-
-       if (rettup == NULL)
-               elog(ERROR, "plperl: SPI_modifytuple() failed - RC = %d\n", SPI_result);
-
-       ckfree(ret_values);
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-
-       return rettup;
+       MemoryContextReset(current_call_data->tmp_cxt);
 }
 
 
-/**********************************************************************
- * plperl_elog()               - elog() support for PLTcl
- **********************************************************************/
-static int
-plperl_elog(ClientData cdata, Tcl_Interp *interp,
-                       int argc, char *argv[])
+SV *
+plperl_spi_query(char *query)
 {
-       int                     level;
-       sigjmp_buf      save_restart;
+       SV                 *cursor;
 
-       /************************************************************
-        * Suppress messages during the restart process
-        ************************************************************/
-       if (plperl_restart_in_progress)
-               return TCL_ERROR;
+       /*
+        * Execute the query inside a sub-transaction, so we can cope with errors
+        * sanely
+        */
+       MemoryContext oldcontext = CurrentMemoryContext;
+       ResourceOwner oldowner = CurrentResourceOwner;
 
-       /************************************************************
-        * Catch the restart longjmp and begin a controlled
-        * return though all interpreter levels if it happens
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               return TCL_ERROR;
-       }
+       BeginInternalSubTransaction(NULL);
+       /* Want to run inside function's memory context */
+       MemoryContextSwitchTo(oldcontext);
 
-       if (argc != 3)
+       PG_TRY();
        {
-               Tcl_SetResult(interp, "syntax error - 'elog level msg'",
-                                         TCL_VOLATILE);
-               return TCL_ERROR;
-       }
+               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);
+               if (portal)
+                       cursor = newSVpv(portal->name, 0);
+               else
+                       cursor = newSV(0);
 
-       if (strcmp(argv[1], "NOTICE") == 0)
-               level = NOTICE;
-       else if (strcmp(argv[1], "WARN") == 0)
-               level = ERROR;
-       else if (strcmp(argv[1], "ERROR") == 0)
-               level = ERROR;
-       else if (strcmp(argv[1], "FATAL") == 0)
-               level = FATAL;
-       else if (strcmp(argv[1], "DEBUG") == 0)
-               level = DEBUG;
-       else if (strcmp(argv[1], "NOIND") == 0)
-               level = NOIND;
-       else
-       {
-               Tcl_AppendResult(interp, "Unknown elog level '", argv[1],
-                                                "'", NULL);
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               return TCL_ERROR;
-       }
+               /* Commit the inner transaction, return to outer xact context */
+               ReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-       /************************************************************
-        * Call elog(), restore the original restart address
-        * and return to the caller (if not catched)
-        ************************************************************/
-       elog(level, argv[2]);
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       return TCL_OK;
-}
+               /*
+                * 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();
 
-/**********************************************************************
- * plperl_quote()      - quote literal strings that are to
- *                       be used in SPI_exec query strings
- **********************************************************************/
-static int
-plperl_quote(ClientData cdata, Tcl_Interp *interp,
-                        int argc, char *argv[])
-{
-       char       *tmp;
-       char       *cp1;
-       char       *cp2;
+               /* Abort the inner transaction */
+               RollbackAndReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-       /************************************************************
-        * Check call syntax
-        ************************************************************/
-       if (argc != 2)
-       {
-               Tcl_SetResult(interp, "syntax error - 'quote string'", TCL_VOLATILE);
-               return TCL_ERROR;
-       }
+               /*
+                * 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();
 
-       /************************************************************
-        * Allocate space for the maximum the string can
-        * grow to and initialize pointers
-        ************************************************************/
-       tmp = palloc(strlen(argv[1]) * 2 + 1);
-       cp1 = argv[1];
-       cp2 = tmp;
+               /* Punt the error to Perl */
+               croak("%s", edata->message);
 
-       /************************************************************
-        * Walk through string and double every quote and backslash
-        ************************************************************/
-       while (*cp1)
-       {
-               if (*cp1 == '\'')
-                       *cp2++ = '\'';
-               else
-               {
-                       if (*cp1 == '\\')
-                               *cp2++ = '\\';
-               }
-               *cp2++ = *cp1++;
+               /* Can't get here, but keep compiler quiet */
+               return NULL;
        }
+       PG_END_TRY();
 
-       /************************************************************
-        * Terminate the string and set it as result
-        ************************************************************/
-       *cp2 = '\0';
-       Tcl_SetResult(interp, tmp, TCL_VOLATILE);
-       pfree(tmp);
-       return TCL_OK;
+       return cursor;
 }
 
 
-/**********************************************************************
- * plperl_SPI_exec()           - The builtin SPI_exec command
- *                               for the safe interpreter
- **********************************************************************/
-static int
-plperl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
-                               int argc, char *argv[])
+SV *
+plperl_spi_fetchrow(char *cursor)
 {
-       int                     spi_rc;
-       char            buf[64];
-       int                     count = 0;
-       char       *arrayname = NULL;
-       int                     query_idx;
-       int                     i;
-       int                     loop_rc;
-       int                     ntuples;
-       HeapTuple  *tuples;
-       TupleDesc       tupdesc = NULL;
-       sigjmp_buf      save_restart;
+       SV                 *row;
 
-       char       *usage = "syntax error - 'SPI_exec "
-       "?-count n? "
-       "?-array name? query ?loop body?";
+       /*
+        * Execute the FETCH inside a sub-transaction, so we can cope with errors
+        * sanely
+        */
+       MemoryContext oldcontext = CurrentMemoryContext;
+       ResourceOwner oldowner = CurrentResourceOwner;
 
-       /************************************************************
-        * Don't do anything if we are already in restart mode
-        ************************************************************/
-       if (plperl_restart_in_progress)
-               return TCL_ERROR;
+       BeginInternalSubTransaction(NULL);
+       /* Want to run inside function's memory context */
+       MemoryContextSwitchTo(oldcontext);
 
-       /************************************************************
-        * Check the call syntax and get the count option
-        ************************************************************/
-       if (argc < 2)
+       PG_TRY();
        {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-               return TCL_ERROR;
-       }
+               Portal          p = SPI_cursor_find(cursor);
 
-       i = 1;
-       while (i < argc)
-       {
-               if (strcmp(argv[i], "-array") == 0)
+               if (!p)
+                       row = newSV(0);
+               else
                {
-                       if (++i >= argc)
+                       SPI_cursor_fetch(p, true, 1);
+                       if (SPI_processed == 0)
                        {
-                               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-                               return TCL_ERROR;
+                               SPI_cursor_close(p);
+                               row = newSV(0);
                        }
-                       arrayname = argv[i++];
-                       continue;
-               }
-
-               if (strcmp(argv[i], "-count") == 0)
-               {
-                       if (++i >= argc)
+                       else
                        {
-                               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-                               return TCL_ERROR;
+                               row = plperl_hash_from_tuple(SPI_tuptable->vals[0],
+                                                                                        SPI_tuptable->tupdesc);
                        }
-                       if (Tcl_GetInt(interp, argv[i++], &count) != TCL_OK)
-                               return TCL_ERROR;
-                       continue;
+                       SPI_freetuptable(SPI_tuptable);
                }
 
-               break;
-       }
-
-       query_idx = i;
-       if (query_idx >= argc)
-       {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Prepare to start a controlled return through all
-        * interpreter levels on transaction abort
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               Tcl_SetResult(interp, "Transaction abort", TCL_VOLATILE);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Execute the query and handle return codes
-        ************************************************************/
-       spi_rc = SPI_exec(argv[query_idx], count);
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-
-       switch (spi_rc)
-       {
-               case SPI_OK_UTILITY:
-                       Tcl_SetResult(interp, "0", TCL_VOLATILE);
-                       return TCL_OK;
-
-               case SPI_OK_SELINTO:
-               case SPI_OK_INSERT:
-               case SPI_OK_DELETE:
-               case SPI_OK_UPDATE:
-                       sprintf(buf, "%d", SPI_processed);
-                       Tcl_SetResult(interp, buf, TCL_VOLATILE);
-                       return TCL_OK;
-
-               case SPI_OK_SELECT:
-                       break;
-
-               case SPI_ERROR_ARGUMENT:
-                       Tcl_SetResult(interp,
-                                               "plperl: SPI_exec() failed - SPI_ERROR_ARGUMENT",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_UNCONNECTED:
-                       Tcl_SetResult(interp,
-                                        "plperl: SPI_exec() failed - SPI_ERROR_UNCONNECTED",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_COPY:
-                       Tcl_SetResult(interp,
-                                                 "plperl: SPI_exec() failed - SPI_ERROR_COPY",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_CURSOR:
-                       Tcl_SetResult(interp,
-                                                 "plperl: SPI_exec() failed - SPI_ERROR_CURSOR",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_TRANSACTION:
-                       Tcl_SetResult(interp,
-                                        "plperl: SPI_exec() failed - SPI_ERROR_TRANSACTION",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_OPUNKNOWN:
-                       Tcl_SetResult(interp,
-                                          "plperl: SPI_exec() failed - SPI_ERROR_OPUNKNOWN",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               default:
-                       sprintf(buf, "%d", spi_rc);
-                       Tcl_AppendResult(interp, "plperl: SPI_exec() failed - ",
-                                                        "unknown RC ", buf, NULL);
-                       return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Only SELECT queries fall through to here - remember the
-        * tuples we got
-        ************************************************************/
-
-       ntuples = SPI_processed;
-       if (ntuples > 0)
-       {
-               tuples = SPI_tuptable->vals;
-               tupdesc = SPI_tuptable->tupdesc;
-       }
-
-       /************************************************************
-        * Again prepare for elog(ERROR)
-        ************************************************************/
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               Tcl_SetResult(interp, "Transaction abort", TCL_VOLATILE);
-               return TCL_ERROR;
-       }
+               /* Commit the inner transaction, return to outer xact context */
+               ReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-       /************************************************************
-        * If there is no loop body given, just set the variables
-        * from the first tuple (if any) and return the number of
-        * tuples selected
-        ************************************************************/
-       if (argc == query_idx + 1)
-       {
-               if (ntuples > 0)
-                       plperl_set_tuple_values(interp, arrayname, 0, tuples[0], tupdesc);
-               sprintf(buf, "%d", ntuples);
-               Tcl_SetResult(interp, buf, TCL_VOLATILE);
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               return TCL_OK;
+               /*
+                * AtEOSubXact_SPI() should not have popped any SPI context, but just
+                * in case it did, make sure we remain connected.
+                */
+               SPI_restore_connection();
        }
-
-       /************************************************************
-        * There is a loop body - process all tuples and evaluate
-        * the body on each
-        ************************************************************/
-       query_idx++;
-       for (i = 0; i < ntuples; i++)
+       PG_CATCH();
        {
-               plperl_set_tuple_values(interp, arrayname, i, tuples[i], tupdesc);
-
-               loop_rc = Tcl_Eval(interp, argv[query_idx]);
-
-               if (loop_rc == TCL_OK)
-                       continue;
-               if (loop_rc == TCL_CONTINUE)
-                       continue;
-               if (loop_rc == TCL_RETURN)
-               {
-                       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-                       return TCL_RETURN;
-               }
-               if (loop_rc == TCL_BREAK)
-                       break;
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               return TCL_ERROR;
-       }
+               ErrorData  *edata;
 
-       /************************************************************
-        * Finally return the number of tuples
-        ************************************************************/
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       sprintf(buf, "%d", ntuples);
-       Tcl_SetResult(interp, buf, TCL_VOLATILE);
-       return TCL_OK;
-}
+               /* Save error info */
+               MemoryContextSwitchTo(oldcontext);
+               edata = CopyErrorData();
+               FlushErrorState();
 
+               /* Abort the inner transaction */
+               RollbackAndReleaseCurrentSubTransaction();
+               MemoryContextSwitchTo(oldcontext);
+               CurrentResourceOwner = oldowner;
 
-/**********************************************************************
- * plperl_SPI_prepare()                - Builtin support for prepared plans
- *                               The Tcl command SPI_prepare
- *                               allways saves the plan using
- *                               SPI_saveplan and returns a key for
- *                               access. There is no chance to prepare
- *                               and not save the plan currently.
- **********************************************************************/
-static int
-plperl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
-                                  int argc, char *argv[])
-{
-       int                     nargs;
-       char      **args;
-       plperl_query_desc *qdesc;
-       void       *plan;
-       int                     i;
-       HeapTuple       typeTup;
-       Tcl_HashEntry *hashent;
-       int                     hashnew;
-       sigjmp_buf      save_restart;
+               /*
+                * 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();
 
-       /************************************************************
-        * Don't do anything if we are already in restart mode
-        ************************************************************/
-       if (plperl_restart_in_progress)
-               return TCL_ERROR;
+               /* Punt the error to Perl */
+               croak("%s", edata->message);
 
-       /************************************************************
-        * Check the call syntax
-        ************************************************************/
-       if (argc != 3)
-       {
-               Tcl_SetResult(interp, "syntax error - 'SPI_prepare query argtypes'",
-                                         TCL_VOLATILE);
-               return TCL_ERROR;
+               /* Can't get here, but keep compiler quiet */
+               return NULL;
        }
+       PG_END_TRY();
 
-       /************************************************************
-        * Split the argument type list
-        ************************************************************/
-       if (Tcl_SplitList(interp, argv[2], &nargs, &args) != TCL_OK)
-               return TCL_ERROR;
-
-       /************************************************************
-        * Allocate the new querydesc structure
-        ************************************************************/
-       qdesc = (plperl_query_desc *) malloc(sizeof(plperl_query_desc));
-       sprintf(qdesc->qname, "%lx", (long) qdesc);
-       qdesc->nargs = nargs;
-       qdesc->argtypes = (Oid *) malloc(nargs * sizeof(Oid));
-       qdesc->arginfuncs = (FmgrInfo *) malloc(nargs * sizeof(FmgrInfo));
-       qdesc->argtypelems = (Oid *) malloc(nargs * sizeof(Oid));
-       qdesc->argvalues = (Datum *) malloc(nargs * sizeof(Datum));
-       qdesc->arglen = (int *) malloc(nargs * sizeof(int));
-
-       /************************************************************
-        * Prepare to start a controlled return through all
-        * interpreter levels on transaction abort
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               free(qdesc->argtypes);
-               free(qdesc->arginfuncs);
-               free(qdesc->argtypelems);
-               free(qdesc->argvalues);
-               free(qdesc->arglen);
-               free(qdesc);
-               ckfree(args);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Lookup the argument types by name in the system cache
-        * and remember the required information for input conversion
-        ************************************************************/
-       for (i = 0; i < nargs; i++)
-       {
-               typeTup = SearchSysCacheTuple(TYPNAME,
-                                                                         PointerGetDatum(args[i]),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(typeTup))
-                       elog(ERROR, "plperl: Cache lookup of type %s failed", args[i]);
-               qdesc->argtypes[i] = typeTup->t_data->t_oid;
-               fmgr_info(((Form_pg_type) GETSTRUCT(typeTup))->typinput,
-                                 &(qdesc->arginfuncs[i]));
-               qdesc->argtypelems[i] = ((Form_pg_type) GETSTRUCT(typeTup))->typelem;
-               qdesc->argvalues[i] = (Datum) NULL;
-               qdesc->arglen[i] = (int) (((Form_pg_type) GETSTRUCT(typeTup))->typlen);
-       }
-
-       /************************************************************
-        * Prepare the plan and check for errors
-        ************************************************************/
-       plan = SPI_prepare(argv[1], nargs, qdesc->argtypes);
-
-       if (plan == NULL)
-       {
-               char            buf[128];
-               char       *reason;
-
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-
-               switch (SPI_result)
-               {
-                       case SPI_ERROR_ARGUMENT:
-                               reason = "SPI_ERROR_ARGUMENT";
-                               break;
-
-                       case SPI_ERROR_UNCONNECTED:
-                               reason = "SPI_ERROR_UNCONNECTED";
-                               break;
-
-                       case SPI_ERROR_COPY:
-                               reason = "SPI_ERROR_COPY";
-                               break;
-
-                       case SPI_ERROR_CURSOR:
-                               reason = "SPI_ERROR_CURSOR";
-                               break;
-
-                       case SPI_ERROR_TRANSACTION:
-                               reason = "SPI_ERROR_TRANSACTION";
-                               break;
-
-                       case SPI_ERROR_OPUNKNOWN:
-                               reason = "SPI_ERROR_OPUNKNOWN";
-                               break;
-
-                       default:
-                               sprintf(buf, "unknown RC %d", SPI_result);
-                               reason = buf;
-                               break;
-
-               }
-
-               elog(ERROR, "plperl: SPI_prepare() failed - %s", reason);
-       }
-
-       /************************************************************
-        * Save the plan
-        ************************************************************/
-       qdesc->plan = SPI_saveplan(plan);
-       if (qdesc->plan == NULL)
-       {
-               char            buf[128];
-               char       *reason;
-
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-
-               switch (SPI_result)
-               {
-                       case SPI_ERROR_ARGUMENT:
-                               reason = "SPI_ERROR_ARGUMENT";
-                               break;
-
-                       case SPI_ERROR_UNCONNECTED:
-                               reason = "SPI_ERROR_UNCONNECTED";
-                               break;
-
-                       default:
-                               sprintf(buf, "unknown RC %d", SPI_result);
-                               reason = buf;
-                               break;
-
-               }
-
-               elog(ERROR, "plperl: SPI_saveplan() failed - %s", reason);
-       }
-
-       /************************************************************
-        * Insert a hashtable entry for the plan and return
-        * the key to the caller
-        ************************************************************/
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       hashent = Tcl_CreateHashEntry(plperl_query_hash, qdesc->qname, &hashnew);
-       Tcl_SetHashValue(hashent, (ClientData) qdesc);
-
-       Tcl_SetResult(interp, qdesc->qname, TCL_VOLATILE);
-       return TCL_OK;
-}
-
-
-/**********************************************************************
- * plperl_SPI_execp()          - Execute a prepared plan
- **********************************************************************/
-static int
-plperl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
-                                int argc, char *argv[])
-{
-       int                     spi_rc;
-       char            buf[64];
-       int                     i,
-                               j;
-       int                     loop_body;
-       Tcl_HashEntry *hashent;
-       plperl_query_desc *qdesc;
-       char       *nulls = NULL;
-       char       *arrayname = NULL;
-       int                     count = 0;
-       int                     callnargs;
-       static char **callargs = NULL;
-       int                     loop_rc;
-       int                     ntuples;
-       HeapTuple  *tuples = NULL;
-       TupleDesc       tupdesc = NULL;
-       sigjmp_buf      save_restart;
-
-       char       *usage = "syntax error - 'SPI_execp "
-       "?-nulls string? ?-count n? "
-       "?-array name? query ?args? ?loop body?";
-
-       /************************************************************
-        * Tidy up from an earlier abort
-        ************************************************************/
-       if (callargs != NULL)
-       {
-               ckfree(callargs);
-               callargs = NULL;
-       }
-
-       /************************************************************
-        * Don't do anything if we are already in restart mode
-        ************************************************************/
-       if (plperl_restart_in_progress)
-               return TCL_ERROR;
-
-       /************************************************************
-        * Get the options and check syntax
-        ************************************************************/
-       i = 1;
-       while (i < argc)
-       {
-               if (strcmp(argv[i], "-array") == 0)
-               {
-                       if (++i >= argc)
-                       {
-                               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-                               return TCL_ERROR;
-                       }
-                       arrayname = argv[i++];
-                       continue;
-               }
-               if (strcmp(argv[i], "-nulls") == 0)
-               {
-                       if (++i >= argc)
-                       {
-                               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-                               return TCL_ERROR;
-                       }
-                       nulls = argv[i++];
-                       continue;
-               }
-               if (strcmp(argv[i], "-count") == 0)
-               {
-                       if (++i >= argc)
-                       {
-                               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-                               return TCL_ERROR;
-                       }
-                       if (Tcl_GetInt(interp, argv[i++], &count) != TCL_OK)
-                               return TCL_ERROR;
-                       continue;
-               }
-
-               break;
-       }
-
-       /************************************************************
-        * Check minimum call arguments
-        ************************************************************/
-       if (i >= argc)
-       {
-               Tcl_SetResult(interp, usage, TCL_VOLATILE);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Get the prepared plan descriptor by it's key
-        ************************************************************/
-       hashent = Tcl_FindHashEntry(plperl_query_hash, argv[i++]);
-       if (hashent == NULL)
-       {
-               Tcl_AppendResult(interp, "invalid queryid '", argv[--i], "'", NULL);
-               return TCL_ERROR;
-       }
-       qdesc = (plperl_query_desc *) Tcl_GetHashValue(hashent);
-
-       /************************************************************
-        * If a nulls string is given, check for correct length
-        ************************************************************/
-       if (nulls != NULL)
-       {
-               if (strlen(nulls) != qdesc->nargs)
-               {
-                       Tcl_SetResult(interp,
-                                  "length of nulls string doesn't match # of arguments",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-               }
-       }
-
-       /************************************************************
-        * If there was a argtype list on preparation, we need
-        * an argument value list now
-        ************************************************************/
-       if (qdesc->nargs > 0)
-       {
-               if (i >= argc)
-               {
-                       Tcl_SetResult(interp, "missing argument list", TCL_VOLATILE);
-                       return TCL_ERROR;
-               }
-
-               /************************************************************
-                * Split the argument values
-                ************************************************************/
-               if (Tcl_SplitList(interp, argv[i++], &callnargs, &callargs) != TCL_OK)
-                       return TCL_ERROR;
-
-               /************************************************************
-                * Check that the # of arguments matches
-                ************************************************************/
-               if (callnargs != qdesc->nargs)
-               {
-                       Tcl_SetResult(interp,
-                       "argument list length doesn't match # of arguments for query",
-                                                 TCL_VOLATILE);
-                       if (callargs != NULL)
-                       {
-                               ckfree(callargs);
-                               callargs = NULL;
-                       }
-                       return TCL_ERROR;
-               }
-
-               /************************************************************
-                * Prepare to start a controlled return through all
-                * interpreter levels on transaction abort during the
-                * parse of the arguments
-                ************************************************************/
-               memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-               if (sigsetjmp(Warn_restart, 1) != 0)
-               {
-                       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-                       for (j = 0; j < callnargs; j++)
-                       {
-                               if (qdesc->arglen[j] < 0 &&
-                                       qdesc->argvalues[j] != (Datum) NULL)
-                               {
-                                       pfree((char *) (qdesc->argvalues[j]));
-                                       qdesc->argvalues[j] = (Datum) NULL;
-                               }
-                       }
-                       ckfree(callargs);
-                       callargs = NULL;
-                       plperl_restart_in_progress = 1;
-                       Tcl_SetResult(interp, "Transaction abort", TCL_VOLATILE);
-                       return TCL_ERROR;
-               }
-
-               /************************************************************
-                * Setup the value array for the SPI_execp() using
-                * the type specific input functions
-                ************************************************************/
-               for (j = 0; j < callnargs; j++)
-               {
-                       qdesc->argvalues[j] = (Datum) (*fmgr_faddr(&qdesc->arginfuncs[j]))
-                               (callargs[j],
-                                qdesc->argtypelems[j],
-                                qdesc->arglen[j]);
-               }
-
-               /************************************************************
-                * Free the splitted argument value list
-                ************************************************************/
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               ckfree(callargs);
-               callargs = NULL;
-       }
-       else
-               callnargs = 0;
-
-       /************************************************************
-        * Remember the index of the last processed call
-        * argument - a loop body for SELECT might follow
-        ************************************************************/
-       loop_body = i;
-
-       /************************************************************
-        * Prepare to start a controlled return through all
-        * interpreter levels on transaction abort
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               for (j = 0; j < callnargs; j++)
-               {
-                       if (qdesc->arglen[j] < 0 && qdesc->argvalues[j] != (Datum) NULL)
-                       {
-                               pfree((char *) (qdesc->argvalues[j]));
-                               qdesc->argvalues[j] = (Datum) NULL;
-                       }
-               }
-               plperl_restart_in_progress = 1;
-               Tcl_SetResult(interp, "Transaction abort", TCL_VOLATILE);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Execute the plan
-        ************************************************************/
-       spi_rc = SPI_execp(qdesc->plan, qdesc->argvalues, nulls, count);
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-
-       /************************************************************
-        * For varlena data types, free the argument values
-        ************************************************************/
-       for (j = 0; j < callnargs; j++)
-       {
-               if (qdesc->arglen[j] < 0 && qdesc->argvalues[j] != (Datum) NULL)
-               {
-                       pfree((char *) (qdesc->argvalues[j]));
-                       qdesc->argvalues[j] = (Datum) NULL;
-               }
-       }
-
-       /************************************************************
-        * Check the return code from SPI_execp()
-        ************************************************************/
-       switch (spi_rc)
-       {
-               case SPI_OK_UTILITY:
-                       Tcl_SetResult(interp, "0", TCL_VOLATILE);
-                       return TCL_OK;
-
-               case SPI_OK_SELINTO:
-               case SPI_OK_INSERT:
-               case SPI_OK_DELETE:
-               case SPI_OK_UPDATE:
-                       sprintf(buf, "%d", SPI_processed);
-                       Tcl_SetResult(interp, buf, TCL_VOLATILE);
-                       return TCL_OK;
-
-               case SPI_OK_SELECT:
-                       break;
-
-               case SPI_ERROR_ARGUMENT:
-                       Tcl_SetResult(interp,
-                                               "plperl: SPI_exec() failed - SPI_ERROR_ARGUMENT",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_UNCONNECTED:
-                       Tcl_SetResult(interp,
-                                        "plperl: SPI_exec() failed - SPI_ERROR_UNCONNECTED",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_COPY:
-                       Tcl_SetResult(interp,
-                                                 "plperl: SPI_exec() failed - SPI_ERROR_COPY",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_CURSOR:
-                       Tcl_SetResult(interp,
-                                                 "plperl: SPI_exec() failed - SPI_ERROR_CURSOR",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_TRANSACTION:
-                       Tcl_SetResult(interp,
-                                        "plperl: SPI_exec() failed - SPI_ERROR_TRANSACTION",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               case SPI_ERROR_OPUNKNOWN:
-                       Tcl_SetResult(interp,
-                                          "plperl: SPI_exec() failed - SPI_ERROR_OPUNKNOWN",
-                                                 TCL_VOLATILE);
-                       return TCL_ERROR;
-
-               default:
-                       sprintf(buf, "%d", spi_rc);
-                       Tcl_AppendResult(interp, "plperl: SPI_exec() failed - ",
-                                                        "unknown RC ", buf, NULL);
-                       return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Only SELECT queries fall through to here - remember the
-        * tuples we got
-        ************************************************************/
-
-       ntuples = SPI_processed;
-       if (ntuples > 0)
-       {
-               tuples = SPI_tuptable->vals;
-               tupdesc = SPI_tuptable->tupdesc;
-       }
-
-       /************************************************************
-        * Prepare to start a controlled return through all
-        * interpreter levels on transaction abort during
-        * the ouput conversions of the results
-        ************************************************************/
-       memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-       if (sigsetjmp(Warn_restart, 1) != 0)
-       {
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               plperl_restart_in_progress = 1;
-               Tcl_SetResult(interp, "Transaction abort", TCL_VOLATILE);
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * If there is no loop body given, just set the variables
-        * from the first tuple (if any) and return the number of
-        * tuples selected
-        ************************************************************/
-       if (loop_body >= argc)
-       {
-               if (ntuples > 0)
-                       plperl_set_tuple_values(interp, arrayname, 0, tuples[0], tupdesc);
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               sprintf(buf, "%d", ntuples);
-               Tcl_SetResult(interp, buf, TCL_VOLATILE);
-               return TCL_OK;
-       }
-
-       /************************************************************
-        * There is a loop body - process all tuples and evaluate
-        * the body on each
-        ************************************************************/
-       for (i = 0; i < ntuples; i++)
-       {
-               plperl_set_tuple_values(interp, arrayname, i, tuples[i], tupdesc);
-
-               loop_rc = Tcl_Eval(interp, argv[loop_body]);
-
-               if (loop_rc == TCL_OK)
-                       continue;
-               if (loop_rc == TCL_CONTINUE)
-                       continue;
-               if (loop_rc == TCL_RETURN)
-               {
-                       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-                       return TCL_RETURN;
-               }
-               if (loop_rc == TCL_BREAK)
-                       break;
-               memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-               return TCL_ERROR;
-       }
-
-       /************************************************************
-        * Finally return the number of tuples
-        ************************************************************/
-       memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
-       sprintf(buf, "%d", ntuples);
-       Tcl_SetResult(interp, buf, TCL_VOLATILE);
-       return TCL_OK;
-}
-
-
-/**********************************************************************
- * plperl_set_tuple_values() - Set variables for all attributes
- *                               of a given tuple
- **********************************************************************/
-static void
-plperl_set_tuple_values(Tcl_Interp *interp, char *arrayname,
-                                               int tupno, HeapTuple tuple, TupleDesc tupdesc)
-{
-       int                     i;
-       char       *outputstr;
-       char            buf[64];
-       Datum           attr;
-       bool            isnull;
-
-       char       *attname;
-       HeapTuple       typeTup;
-       Oid                     typoutput;
-       Oid                     typelem;
-
-       char      **arrptr;
-       char      **nameptr;
-       char       *nullname = NULL;
-
-       /************************************************************
-        * Prepare pointers for Tcl_SetVar2() below and in array
-        * mode set the .tupno element
-        ************************************************************/
-       if (arrayname == NULL)
-       {
-               arrptr = &attname;
-               nameptr = &nullname;
-       }
-       else
-       {
-               arrptr = &arrayname;
-               nameptr = &attname;
-               sprintf(buf, "%d", tupno);
-               Tcl_SetVar2(interp, arrayname, ".tupno", buf, 0);
-       }
-
-       for (i = 0; i < tupdesc->natts; i++)
-       {
-               /************************************************************
-                * Get the attribute name
-                ************************************************************/
-               attname = tupdesc->attrs[i]->attname.data;
-
-               /************************************************************
-                * Get the attributes value
-                ************************************************************/
-               attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
-
-               /************************************************************
-                * Lookup the attribute type in the syscache
-                * for the output function
-                ************************************************************/
-               typeTup = SearchSysCacheTuple(TYPEOID,
-                                                  ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(typeTup))
-               {
-                       elog(ERROR, "plperl: Cache lookup for attribute '%s' type %ld failed",
-                                attname, ObjectIdGetDatum(tupdesc->attrs[i]->atttypid));
-               }
-
-               typoutput = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typoutput);
-               typelem = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typelem);
-
-               /************************************************************
-                * If there is a value, set the variable
-                * If not, unset it
-                *
-                * Hmmm - Null attributes will cause functions to
-                *                crash if they don't expect them - need something
-                *                smarter here.
-                ************************************************************/
-               if (!isnull && OidIsValid(typoutput))
-               {
-                       FmgrInfo        finfo;
-
-                       fmgr_info(typoutput, &finfo);
-
-                       outputstr = (*fmgr_faddr(&finfo))
-                               (attr, typelem,
-                                tupdesc->attrs[i]->attlen);
-
-                       Tcl_SetVar2(interp, *arrptr, *nameptr, outputstr, 0);
-                       pfree(outputstr);
-               }
-               else
-                       Tcl_UnsetVar2(interp, *arrptr, *nameptr, 0);
-       }
-}
-
-
-#endif
-/**********************************************************************
- * plperl_build_tuple_argument() - Build a string for a ref to a hash
- *                               from all attributes of a given tuple
- **********************************************************************/
-static SV  *
-plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
-{
-       int                     i;
-       SV                 *output;
-       Datum           attr;
-       bool            isnull;
-
-       char       *attname;
-       char       *outputstr;
-       HeapTuple       typeTup;
-       Oid                     typoutput;
-       Oid                     typelem;
-
-       output = sv_2mortal(newSVpv("{", 0));
-
-       for (i = 0; i < tupdesc->natts; i++)
-       {
-               /************************************************************
-                * Get the attribute name
-                ************************************************************/
-               attname = tupdesc->attrs[i]->attname.data;
-
-               /************************************************************
-                * Get the attributes value
-                ************************************************************/
-               attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
-
-               /************************************************************
-                * Lookup the attribute type in the syscache
-                * for the output function
-                ************************************************************/
-               typeTup = SearchSysCacheTuple(TYPEOID,
-                                                  ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
-                                                                         0, 0, 0);
-               if (!HeapTupleIsValid(typeTup))
-               {
-                       elog(ERROR, "plperl: Cache lookup for attribute '%s' type %ld failed",
-                                attname, ObjectIdGetDatum(tupdesc->attrs[i]->atttypid));
-               }
-
-               typoutput = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typoutput);
-               typelem = (Oid) (((Form_pg_type) GETSTRUCT(typeTup))->typelem);
-
-               /************************************************************
-                * If there is a value, append the attribute name and the
-                * value to the list.
-                *      If it is null it will be set to undef.
-                ************************************************************/
-               if (!isnull && OidIsValid(typoutput))
-               {
-                       FmgrInfo        finfo;
-
-                       fmgr_info(typoutput, &finfo);
-
-                       outputstr = (*fmgr_faddr(&finfo))
-                               (attr, typelem,
-                                tupdesc->attrs[i]->attlen);
-
-                       sv_catpvf(output, "'%s' => '%s',", attname, outputstr);
-                       pfree(outputstr);
-               }
-               else
-                       sv_catpvf(output, "'%s' => undef,", attname);
-       }
-       sv_catpv(output, "}");
-       output = perl_eval_pv(SvPV(output, na), TRUE);
-       return output;
+       return row;
 }