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