* 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)))));
}
/*
*/
subref = newSVsv(POPs);
- if (!SvROK(subref))
+ if (!SvROK(subref) || SvTYPE(SvRV(subref)) != SVt_PVCV)
{
PUTBACK;
FREETMPS;
* 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() -
*
*
**********************************************************************/
-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;
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;
}