Commit 28e9b26f authored by Tom Lane's avatar Tom Lane

Further plperl cleanup: be more paranoid about checking the type of

data returned from Perl.  Consolidate multiple bits of code to convert
a Perl hash to a tuple, and drive the conversion off the keys present
in the hash rather than the tuple column names, so we detect error if
the hash contains keys it shouldn't.  (This means keys not in the hash
will silently default to NULL, which seems ok to me.)  Fix a bunch of
reference-count leaks too.
parent 25fcfdf6
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
* ENHANCEMENTS, OR MODIFICATIONS. * ENHANCEMENTS, OR MODIFICATIONS.
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.62 2004/11/22 20:31:53 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.63 2004/11/23 00:21:17 tgl Exp $
* *
**********************************************************************/ **********************************************************************/
...@@ -45,17 +45,10 @@ ...@@ -45,17 +45,10 @@
#include <unistd.h> #include <unistd.h>
/* postgreSQL stuff */ /* postgreSQL stuff */
#include "access/heapam.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "funcapi.h" /* need for SRF support */
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/spi.h" #include "executor/spi.h"
#include "fmgr.h" #include "funcapi.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h" #include "utils/typcache.h"
/* perl stuff */ /* perl stuff */
...@@ -121,7 +114,7 @@ static Datum plperl_func_handler(PG_FUNCTION_ARGS); ...@@ -121,7 +114,7 @@ static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger); static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc); static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
static void plperl_init_shared_libs(pTHX); static void plperl_init_shared_libs(pTHX);
static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int); static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
...@@ -272,26 +265,36 @@ strip_trailing_ws(const char *msg) ...@@ -272,26 +265,36 @@ strip_trailing_ws(const char *msg)
} }
static HV * /*
plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc) * Build a tuple from a hash
*/
static HeapTuple
plperl_build_tuple_result(HV *perlhash, AttInMetadata *attinmeta)
{ {
int i; TupleDesc td = attinmeta->tupdesc;
HV *hv = newHV(); char **values;
for (i = 0; i < tupdesc->natts; i++) SV *val;
{ char *key;
SV *value; I32 klen;
HeapTuple tup;
char *key = SPI_fname(tupdesc, i+1); values = (char **) palloc0(td->natts * sizeof(char *));
char *val = SPI_getvalue(tuple, tupdesc, i + 1);
if (val) hv_iterinit(perlhash);
value = newSVpv(val, 0); while ((val = hv_iternextsv(perlhash, &key, &klen)))
else {
value = newSV(0); int attn = SPI_fnumber(td, key);
hv_store(hv, key, strlen(key), value, 0); if (attn <= 0 || td->attrs[attn - 1]->attisdropped)
elog(ERROR, "plperl: invalid attribute \"%s\" in hash", key);
if (SvTYPE(val) != SVt_NULL)
values[attn - 1] = SvPV(val, PL_na);
} }
return hv; hv_iterinit(perlhash);
tup = BuildTupleFromCStrings(attinmeta, values);
pfree(values);
return tup;
} }
...@@ -303,7 +306,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) ...@@ -303,7 +306,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
{ {
TriggerData *tdata; TriggerData *tdata;
TupleDesc tupdesc; TupleDesc tupdesc;
int i = 0; int i;
char *level; char *level;
char *event; char *event;
char *relid; char *relid;
...@@ -316,8 +319,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) ...@@ -316,8 +319,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
tupdesc = tdata->tg_relation->rd_att; tupdesc = tdata->tg_relation->rd_att;
relid = DatumGetCString( relid = DatumGetCString(
DirectFunctionCall1( DirectFunctionCall1(oidout,
oidout, ObjectIdGetDatum(tdata->tg_relation->rd_id) ObjectIdGetDatum(tdata->tg_relation->rd_id)
) )
); );
...@@ -328,28 +331,24 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) ...@@ -328,28 +331,24 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
{ {
event = "INSERT"; event = "INSERT";
hv_store(hv, "new", 3, hv_store(hv, "new", 3,
newRV((SV *)plperl_hash_from_tuple(tdata->tg_trigtuple, plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
tupdesc)),
0); 0);
} }
else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
{ {
event = "DELETE"; event = "DELETE";
hv_store(hv, "old", 3, hv_store(hv, "old", 3,
newRV((SV *)plperl_hash_from_tuple(tdata->tg_trigtuple, plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
tupdesc)),
0); 0);
} }
else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
{ {
event = "UPDATE"; event = "UPDATE";
hv_store(hv, "old", 3, hv_store(hv, "old", 3,
newRV((SV *)plperl_hash_from_tuple(tdata->tg_trigtuple, plperl_hash_from_tuple(tdata->tg_trigtuple, tupdesc),
tupdesc)),
0); 0);
hv_store(hv, "new", 3, hv_store(hv, "new", 3,
newRV((SV *)plperl_hash_from_tuple(tdata->tg_newtuple, plperl_hash_from_tuple(tdata->tg_newtuple, tupdesc),
tupdesc)),
0); 0);
} }
else { else {
...@@ -364,7 +363,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) ...@@ -364,7 +363,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
AV *av = newAV(); AV *av = newAV();
for (i=0; i < tdata->tg_trigger->tgnargs; i++) for (i=0; i < tdata->tg_trigger->tgnargs; i++)
av_push(av, newSVpv(tdata->tg_trigger->tgargs[i], 0)); av_push(av, newSVpv(tdata->tg_trigger->tgargs[i], 0));
hv_store(hv, "args", 4, newRV((SV *)av), 0); hv_store(hv, "args", 4, newRV_noinc((SV *)av), 0);
} }
hv_store(hv, "relname", 7, hv_store(hv, "relname", 7,
...@@ -386,61 +385,9 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) ...@@ -386,61 +385,9 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
level = "UNKNOWN"; level = "UNKNOWN";
hv_store(hv, "level", 5, newSVpv(level, 0), 0); hv_store(hv, "level", 5, newSVpv(level, 0), 0);
return newRV((SV*)hv); return newRV_noinc((SV*)hv);
}
/**********************************************************************
* extract a list of keys from a hash
**********************************************************************/
static AV *
plperl_get_keys(HV *hv)
{
AV *ret;
SV *val;
char *key;
I32 klen;
ret = newAV();
hv_iterinit(hv);
while ((val = hv_iternextsv(hv, (char **) &key, &klen)))
av_push(ret, newSVpv(key, 0));
hv_iterinit(hv);
return ret;
}
/**********************************************************************
* extract a given key (by index) from a list of keys
**********************************************************************/
static char *
plperl_get_key(AV *keys, int index)
{
SV **svp;
int len;
len = av_len(keys) + 1;
if (index < len)
svp = av_fetch(keys, index, FALSE);
else
return NULL;
return SvPV(*svp, PL_na);
} }
/**********************************************************************
* extract a value for a given key from a hash
*
* return NULL on error or if we got an undef
**********************************************************************/
static char *
plperl_get_elem(HV *hash, char *key)
{
SV **svp = hv_fetch(hash, key, strlen(key), FALSE);
if (!svp)
elog(ERROR, "plperl: key \"%s\" not found", key);
return SvTYPE(*svp) == SVt_NULL ? NULL : SvPV(*svp, PL_na);
}
/* /*
* Obtain tuple descriptor for a function returning tuple * Obtain tuple descriptor for a function returning tuple
...@@ -468,84 +415,78 @@ get_function_tupdesc(Oid result_type, ReturnSetInfo *rsinfo) ...@@ -468,84 +415,78 @@ get_function_tupdesc(Oid result_type, ReturnSetInfo *rsinfo)
* set up the new tuple returned from a trigger * set up the new tuple returned from a trigger
**********************************************************************/ **********************************************************************/
static HeapTuple static HeapTuple
plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup, Oid fn_oid) plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
{ {
SV **svp; SV **svp;
HV *hvNew; HV *hvNew;
AV *plkeys;
char *platt;
char *plval;
HeapTuple rtup; HeapTuple rtup;
int natts, SV *val;
i, char *key;
attn, I32 klen;
atti; int slotsused;
int *volatile modattrs = NULL; int *modattrs;
Datum *volatile modvalues = NULL; Datum *modvalues;
char *volatile modnulls = NULL; char *modnulls;
TupleDesc tupdesc; TupleDesc tupdesc;
HeapTuple typetup;
tupdesc = tdata->tg_relation->rd_att; tupdesc = tdata->tg_relation->rd_att;
svp = hv_fetch(hvTD, "new", 3, FALSE); svp = hv_fetch(hvTD, "new", 3, FALSE);
if (!svp)
elog(ERROR, "plperl: key \"new\" not found");
if (SvTYPE(*svp) != SVt_RV || SvTYPE(SvRV(*svp)) != SVt_PVHV)
elog(ERROR, "plperl: $_TD->{new} is not a hash reference");
hvNew = (HV *) SvRV(*svp); hvNew = (HV *) SvRV(*svp);
if (SvTYPE(hvNew) != SVt_PVHV) modattrs = palloc(tupdesc->natts * sizeof(int));
elog(ERROR, "plperl: $_TD->{new} is not a hash"); modvalues = palloc(tupdesc->natts * sizeof(Datum));
modnulls = palloc(tupdesc->natts * sizeof(char));
slotsused = 0;
plkeys = plperl_get_keys(hvNew); hv_iterinit(hvNew);
natts = av_len(plkeys) + 1; while ((val = hv_iternextsv(hvNew, &key, &klen)))
if (natts != tupdesc->natts) {
elog(ERROR, "plperl: $_TD->{new} has an incorrect number of keys"); int attn = SPI_fnumber(tupdesc, key);
modattrs = palloc0(natts * sizeof(int));
modvalues = palloc0(natts * sizeof(Datum));
modnulls = palloc0(natts * sizeof(char));
for (i = 0; i < natts; i++) if (attn <= 0 || tupdesc->attrs[attn - 1]->attisdropped)
elog(ERROR, "plperl: invalid attribute \"%s\" in hash", key);
if (SvTYPE(val) != SVt_NULL)
{ {
FmgrInfo finfo;
Oid typinput; Oid typinput;
Oid typelem; Oid typioparam;
FmgrInfo finfo;
platt = plperl_get_key(plkeys, i);
attn = modattrs[i] = SPI_fnumber(tupdesc, platt);
if (attn == SPI_ERROR_NOATTRIBUTE)
elog(ERROR, "plperl: invalid attribute \"%s\" in tuple", platt);
atti = attn - 1;
plval = plperl_get_elem(hvNew, platt);
typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(tupdesc->attrs[atti]->atttypid), 0, 0, 0); /* XXX would be better to cache these lookups */
typinput = ((Form_pg_type) GETSTRUCT(typetup))->typinput; getTypeInputInfo(tupdesc->attrs[attn - 1]->atttypid,
typelem = ((Form_pg_type) GETSTRUCT(typetup))->typelem; &typinput, &typioparam);
ReleaseSysCache(typetup);
fmgr_info(typinput, &finfo); fmgr_info(typinput, &finfo);
modvalues[slotsused] = FunctionCall3(&finfo,
if (plval) CStringGetDatum(SvPV(val, PL_na)),
{ ObjectIdGetDatum(typioparam),
modvalues[i] = FunctionCall3(&finfo, Int32GetDatum(tupdesc->attrs[attn - 1]->atttypmod));
CStringGetDatum(plval), modnulls[slotsused] = ' ';
ObjectIdGetDatum(typelem),
Int32GetDatum(tupdesc->attrs[atti]->atttypmod));
modnulls[i] = ' ';
} }
else else
{ {
modvalues[i] = (Datum) 0; modvalues[slotsused] = (Datum) 0;
modnulls[i] = 'n'; modnulls[slotsused] = 'n';
} }
modattrs[slotsused] = attn;
slotsused++;
} }
rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs, modvalues, modnulls); hv_iterinit(hvNew);
rtup = SPI_modifytuple(tdata->tg_relation, otup, slotsused,
modattrs, modvalues, modnulls);
pfree(modattrs); pfree(modattrs);
pfree(modvalues); pfree(modvalues);
pfree(modnulls); pfree(modnulls);
if (rtup == NULL) if (rtup == NULL)
elog(ERROR, "plperl: SPI_modifytuple failed -- error: %d", SPI_result); elog(ERROR, "plperl: SPI_modifytuple failed: %s",
SPI_result_code_string(SPI_result));
return rtup; return rtup;
} }
...@@ -715,7 +656,9 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo) ...@@ -715,7 +656,9 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
SAVETMPS; SAVETMPS;
PUSHMARK(SP); PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVpv("undef", 0)));
XPUSHs(sv_2mortal(newSVpv("undef", 0))); /* no trigger data */
for (i = 0; i < desc->nargs; i++) for (i = 0; i < desc->nargs; i++)
{ {
if (fcinfo->argnull[i]) if (fcinfo->argnull[i])
...@@ -738,9 +681,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo) ...@@ -738,9 +681,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td; tmptup.t_data = td;
/* plperl_build_tuple_argument better return a mortal SV */ hashref = plperl_hash_from_tuple(&tmptup, tupdesc);
hashref = plperl_build_tuple_argument(&tmptup, tupdesc); XPUSHs(sv_2mortal(hashref));
XPUSHs(hashref);
} }
else else
{ {
...@@ -789,11 +731,12 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo) ...@@ -789,11 +731,12 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
} }
/********************************************************************** /**********************************************************************
* plperl_call_perl_trigger_func() - calls a perl function affected by trigger * plperl_call_perl_trigger_func() - calls a perl trigger function
* through the RV stored in the prodesc structure. massages the input parms properly * through the RV stored in the prodesc structure.
**********************************************************************/ **********************************************************************/
static SV * static SV *
plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, SV *td) plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo,
SV *td)
{ {
dSP; dSP;
SV *retval; SV *retval;
...@@ -805,13 +748,16 @@ plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, S ...@@ -805,13 +748,16 @@ plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, S
SAVETMPS; SAVETMPS;
PUSHMARK(sp); PUSHMARK(sp);
XPUSHs(td); XPUSHs(td);
tg_trigger = ((TriggerData *) fcinfo->context)->tg_trigger; tg_trigger = ((TriggerData *) fcinfo->context)->tg_trigger;
for (i = 0; i < tg_trigger->tgnargs; i++) for (i = 0; i < tg_trigger->tgnargs; i++)
XPUSHs(sv_2mortal(newSVpv(tg_trigger->tgargs[i], 0))); XPUSHs(sv_2mortal(newSVpv(tg_trigger->tgargs[i], 0)));
PUTBACK; 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; SPAGAIN;
...@@ -897,21 +843,18 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -897,21 +843,18 @@ plperl_func_handler(PG_FUNCTION_ARGS)
PG_RETURN_NULL(); PG_RETURN_NULL();
} }
if (prodesc->fn_retisset &&
(SvTYPE(perlret) != SVt_RV || SvTYPE(SvRV(perlret)) != SVt_PVAV))
elog(ERROR, "plperl: set-returning function must return reference to array");
if (prodesc->fn_retistuple && SvTYPE(perlret) != SVt_RV)
elog(ERROR, "plperl: composite-returning function must return a reference");
if (prodesc->fn_retisset && prodesc->fn_retistuple) if (prodesc->fn_retisset && prodesc->fn_retistuple)
{ {
/* set of tuples */ /* set of tuples */
AV *ret_av = (AV *) SvRV(perlret); AV *ret_av;
FuncCallContext *funcctx; FuncCallContext *funcctx;
TupleDesc tupdesc; TupleDesc tupdesc;
AttInMetadata *attinmeta; AttInMetadata *attinmeta;
if (SvTYPE(perlret) != SVt_RV || SvTYPE(SvRV(perlret)) != SVt_PVAV)
elog(ERROR, "plperl: set-returning function must return reference to array");
ret_av = (AV *) SvRV(perlret);
if (SRF_IS_FIRSTCALL()) if (SRF_IS_FIRSTCALL())
{ {
MemoryContext oldcontext; MemoryContext oldcontext;
...@@ -939,25 +882,16 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -939,25 +882,16 @@ plperl_func_handler(PG_FUNCTION_ARGS)
{ {
SV **svp; SV **svp;
HV *row_hv; HV *row_hv;
char **values;
HeapTuple tuple; HeapTuple tuple;
int i;
svp = av_fetch(ret_av, funcctx->call_cntr, FALSE); svp = av_fetch(ret_av, funcctx->call_cntr, FALSE);
Assert(svp != NULL);
if (SvTYPE(*svp) != SVt_RV) if (SvTYPE(*svp) != SVt_RV || SvTYPE(SvRV(*svp)) != SVt_PVHV)
elog(ERROR, "plperl: check your return value structure"); elog(ERROR, "plperl: element of result array is not a reference to hash");
row_hv = (HV *) SvRV(*svp); row_hv = (HV *) SvRV(*svp);
values = (char **) palloc(tupdesc->natts * sizeof(char *)); tuple = plperl_build_tuple_result(row_hv, attinmeta);
for (i = 0; i < tupdesc->natts; i++)
{
char *column_key;
column_key = SPI_fname(tupdesc, i + 1);
values[i] = plperl_get_elem(row_hv, column_key);
}
tuple = BuildTupleFromCStrings(attinmeta, values);
retval = HeapTupleGetDatum(tuple); retval = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, retval); SRF_RETURN_NEXT(funcctx, retval);
} }
...@@ -970,9 +904,13 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -970,9 +904,13 @@ plperl_func_handler(PG_FUNCTION_ARGS)
else if (prodesc->fn_retisset) else if (prodesc->fn_retisset)
{ {
/* set of non-tuples */ /* set of non-tuples */
AV *ret_av = (AV *) SvRV(perlret); AV *ret_av;
FuncCallContext *funcctx; FuncCallContext *funcctx;
if (SvTYPE(perlret) != SVt_RV || SvTYPE(SvRV(perlret)) != SVt_PVAV)
elog(ERROR, "plperl: set-returning function must return reference to array");
ret_av = (AV *) SvRV(perlret);
if (SRF_IS_FIRSTCALL()) if (SRF_IS_FIRSTCALL())
{ {
funcctx = SRF_FIRSTCALL_INIT(); funcctx = SRF_FIRSTCALL_INIT();
...@@ -989,6 +927,7 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -989,6 +927,7 @@ plperl_func_handler(PG_FUNCTION_ARGS)
SV **svp; SV **svp;
svp = av_fetch(ret_av, funcctx->call_cntr, FALSE); svp = av_fetch(ret_av, funcctx->call_cntr, FALSE);
Assert(svp != NULL);
if (SvTYPE(*svp) != SVt_NULL) if (SvTYPE(*svp) != SVt_NULL)
{ {
...@@ -1016,30 +955,24 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -1016,30 +955,24 @@ plperl_func_handler(PG_FUNCTION_ARGS)
else if (prodesc->fn_retistuple) else if (prodesc->fn_retistuple)
{ {
/* singleton perl hash to Datum */ /* singleton perl hash to Datum */
HV *perlhash = (HV *) SvRV(perlret); HV *perlhash;
TupleDesc td; TupleDesc td;
int i;
char **values;
AttInMetadata *attinmeta; AttInMetadata *attinmeta;
HeapTuple tup; HeapTuple tup;
if (SvTYPE(perlret) != SVt_RV || SvTYPE(SvRV(perlret)) != SVt_PVHV)
elog(ERROR, "plperl: composite-returning function must return a reference to hash");
perlhash = (HV *) SvRV(perlret);
/* /*
* XXX should cache the attinmetadata instead of recomputing * XXX should cache the attinmeta data instead of recomputing
*/ */
td = get_function_tupdesc(prodesc->result_oid, td = get_function_tupdesc(prodesc->result_oid,
(ReturnSetInfo *) fcinfo->resultinfo); (ReturnSetInfo *) fcinfo->resultinfo);
/* td = CreateTupleDescCopy(td); */ /* td = CreateTupleDescCopy(td); */
attinmeta = TupleDescGetAttInMetadata(td); attinmeta = TupleDescGetAttInMetadata(td);
values = (char **) palloc(td->natts * sizeof(char *)); tup = plperl_build_tuple_result(perlhash, attinmeta);
for (i = 0; i < td->natts; i++)
{
char *key;
key = SPI_fname(td, i + 1);
values[i] = plperl_get_elem(perlhash, key);
}
tup = BuildTupleFromCStrings(attinmeta, values);
retval = HeapTupleGetDatum(tup); retval = HeapTupleGetDatum(tup);
} }
else else
...@@ -1066,7 +999,6 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1066,7 +999,6 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
plperl_proc_desc *prodesc; plperl_proc_desc *prodesc;
SV *perlret; SV *perlret;
Datum retval; Datum retval;
char *tmp;
SV *svTD; SV *svTD;
HV *hvTD; HV *hvTD;
...@@ -1092,8 +1024,6 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1092,8 +1024,6 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
hvTD = (HV *) SvRV(svTD); /* convert SV TD structure to Perl Hash hvTD = (HV *) SvRV(svTD); /* convert SV TD structure to Perl Hash
* structure */ * structure */
tmp = SvPV(perlret, PL_na);
/************************************************************ /************************************************************
* Disconnect from SPI manager and then create the return * Disconnect from SPI manager and then create the return
* values datum (if the input function does a palloc for it * values datum (if the input function does a palloc for it
...@@ -1103,8 +1033,9 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1103,8 +1033,9 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
if (SPI_finish() != SPI_OK_FINISH) if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "plperl: SPI_finish() failed"); elog(ERROR, "plperl: SPI_finish() failed");
if (!(perlret && SvOK(perlret))) if (!(perlret && SvOK(perlret) && SvTYPE(perlret) != SVt_NULL))
{ {
/* undef result means go ahead with original tuple */
TriggerData *trigdata = ((TriggerData *) fcinfo->context); TriggerData *trigdata = ((TriggerData *) fcinfo->context);
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
...@@ -1117,46 +1048,42 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1117,46 +1048,42 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
retval = (Datum) 0; /* can this happen? */ retval = (Datum) 0; /* can this happen? */
} }
else else
{
if (!fcinfo->isnull)
{ {
HeapTuple trv; HeapTuple trv;
char *tmp;
if (strcasecmp(tmp, "SKIP") == 0) tmp = SvPV(perlret, PL_na);
if (pg_strcasecmp(tmp, "SKIP") == 0)
trv = NULL; trv = NULL;
else if (strcasecmp(tmp, "MODIFY") == 0) else if (pg_strcasecmp(tmp, "MODIFY") == 0)
{ {
TriggerData *trigdata = (TriggerData *) fcinfo->context; TriggerData *trigdata = (TriggerData *) fcinfo->context;
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
trv = plperl_modify_tuple(hvTD, trigdata, trigdata->tg_trigtuple, fcinfo->flinfo->fn_oid); trv = plperl_modify_tuple(hvTD, trigdata,
trigdata->tg_trigtuple);
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
trv = plperl_modify_tuple(hvTD, trigdata, trigdata->tg_newtuple, fcinfo->flinfo->fn_oid); trv = plperl_modify_tuple(hvTD, trigdata,
trigdata->tg_newtuple);
else else
{ {
elog(WARNING, "plperl: ignoring modified tuple in DELETE trigger");
trv = NULL; trv = NULL;
elog(WARNING, "plperl: Ignoring modified tuple in DELETE trigger");
} }
} }
else if (strcasecmp(tmp, "OK"))
{
trv = NULL;
elog(ERROR, "plperl: Expected return to be undef, 'SKIP' or 'MODIFY'");
}
else else
{ {
elog(ERROR, "plperl: expected trigger result to be undef, \"SKIP\" or \"MODIFY\"");
trv = NULL; trv = NULL;
elog(ERROR, "plperl: Expected return to be undef, 'SKIP' or 'MODIFY'");
} }
retval = PointerGetDatum(trv); retval = PointerGetDatum(trv);
} }
else
retval = (Datum) 0;
}
SvREFCNT_dec(svTD);
if (perlret)
SvREFCNT_dec(perlret); SvREFCNT_dec(perlret);
fcinfo->isnull = false;
return retval; return retval;
} }
...@@ -1408,31 +1335,32 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ...@@ -1408,31 +1335,32 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
/********************************************************************** /**********************************************************************
* plperl_build_tuple_argument() - Build a string for a ref to a hash * plperl_hash_from_tuple() - Build a ref to a hash
* from all attributes of a given tuple * from all attributes of a given tuple
**********************************************************************/ **********************************************************************/
static SV * static SV *
plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
{ {
int i;
HV *hv; HV *hv;
int i;
hv = newHV();
for (i = 0; i < tupdesc->natts; i++)
{
Datum attr; Datum attr;
bool isnull; bool isnull;
char *attname; char *attname;
char *outputstr; char *outputstr;
HeapTuple typeTup;
Oid typoutput; Oid typoutput;
Oid typioparam; Oid typioparam;
bool typisvarlena;
int namelen; int namelen;
hv = newHV();
for (i = 0; i < tupdesc->natts; i++)
{
if (tupdesc->attrs[i]->attisdropped) if (tupdesc->attrs[i]->attisdropped)
continue; continue;
attname = tupdesc->attrs[i]->attname.data; attname = NameStr(tupdesc->attrs[i]->attname);
namelen = strlen(attname); namelen = strlen(attname);
attr = heap_getattr(tuple, i + 1, tupdesc, &isnull); attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
...@@ -1442,24 +1370,11 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) ...@@ -1442,24 +1370,11 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
continue; continue;
} }
/************************************************************ /* XXX should have a way to cache these lookups */
* Lookup the attribute type in the syscache
* for the output function
************************************************************/
typeTup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(tupdesc->attrs[i]->atttypid),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u",
tupdesc->attrs[i]->atttypid);
typoutput = ((Form_pg_type) GETSTRUCT(typeTup))->typoutput; getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
typioparam = getTypeIOParam(typeTup); &typoutput, &typioparam, &typisvarlena);
ReleaseSysCache(typeTup);
/************************************************************
* Append the attribute name and the value to the list.
************************************************************/
outputstr = DatumGetCString(OidFunctionCall3(typoutput, outputstr = DatumGetCString(OidFunctionCall3(typoutput,
attr, attr,
ObjectIdGetDatum(typioparam), ObjectIdGetDatum(typioparam),
...@@ -1468,7 +1383,7 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) ...@@ -1468,7 +1383,7 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
hv_store(hv, attname, namelen, newSVpv(outputstr, 0), 0); hv_store(hv, attname, namelen, newSVpv(outputstr, 0), 0);
} }
return sv_2mortal(newRV((SV *)hv)); return newRV_noinc((SV *) hv);
} }
...@@ -1558,14 +1473,14 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, ...@@ -1558,14 +1473,14 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed,
if (status == SPI_OK_SELECT) if (status == SPI_OK_SELECT)
{ {
AV *rows; AV *rows;
HV *row; SV *row;
int i; int i;
rows = newAV(); rows = newAV();
for (i = 0; i < processed; i++) for (i = 0; i < processed; i++)
{ {
row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc); row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
av_push(rows, newRV_noinc((SV *)row)); av_push(rows, row);
} }
hv_store(result, "rows", strlen("rows"), hv_store(result, "rows", strlen("rows"),
newRV_noinc((SV *) rows), 0); newRV_noinc((SV *) rows), 0);
......
...@@ -119,9 +119,9 @@ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$ ...@@ -119,9 +119,9 @@ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
]; ];
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
SELECT perl_set(); SELECT perl_set();
ERROR: plperl: check your return value structure ERROR: plperl: element of result array is not a reference to hash
SELECT * FROM perl_set(); SELECT * FROM perl_set();
ERROR: plperl: check your return value structure ERROR: plperl: element of result array is not a reference to hash
CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$ CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
return [ return [
{ f1 => 1, f2 => 'Hello', f3 => 'World' }, { f1 => 1, f2 => 'Hello', f3 => 'World' },
...@@ -202,7 +202,7 @@ ERROR: could not determine row description for function returning record ...@@ -202,7 +202,7 @@ ERROR: could not determine row description for function returning record
SELECT * FROM perl_record_set(); SELECT * FROM perl_record_set();
ERROR: a column definition list is required for functions returning "record" ERROR: a column definition list is required for functions returning "record"
SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text); SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
ERROR: plperl: check your return value structure ERROR: plperl: element of result array is not a reference to hash
CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$ CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
return [ return [
{ f1 => 1, f2 => 'Hello', f3 => 'World' }, { f1 => 1, f2 => 'Hello', f3 => 'World' },
...@@ -222,3 +222,81 @@ SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text); ...@@ -222,3 +222,81 @@ SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
3 | Hello | PL/Perl 3 | Hello | PL/Perl
(3 rows) (3 rows)
CREATE TYPE footype AS (x INTEGER, y INTEGER);
CREATE OR REPLACE FUNCTION foo_good() RETURNS SETOF footype AS $$
return [
{x => 1, y => 2},
{x => 3, y => 4}
];
$$ LANGUAGE plperl;
SELECT * FROM foo_good();
x | y
---+---
1 | 2
3 | 4
(2 rows)
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return {y => 3, z => 4};
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
ERROR: plperl: invalid attribute "z" in hash
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return 42;
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
ERROR: plperl: composite-returning function must return a reference to hash
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return [
[1, 2],
[3, 4]
];
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
ERROR: plperl: composite-returning function must return a reference to hash
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return 42;
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
ERROR: plperl: set-returning function must return reference to array
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return {y => 3, z => 4};
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
ERROR: plperl: set-returning function must return reference to array
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return [
[1, 2],
[3, 4]
];
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
ERROR: plperl: element of result array is not a reference to hash
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return [
{y => 3, z => 4}
];
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
ERROR: plperl: invalid attribute "z" in hash
CREATE OR REPLACE FUNCTION perl_get_field(footype, text) RETURNS integer AS $$
return $_[0]->{$_[1]};
$$ LANGUAGE plperl;
SELECT perl_get_field((11,12), 'x');
perl_get_field
----------------
11
(1 row)
SELECT perl_get_field((11,12), 'y');
perl_get_field
----------------
12
(1 row)
SELECT perl_get_field((11,12), 'z');
perl_get_field
----------------
(1 row)
...@@ -134,3 +134,80 @@ $$ LANGUAGE plperl; ...@@ -134,3 +134,80 @@ $$ LANGUAGE plperl;
SELECT perl_record_set(); SELECT perl_record_set();
SELECT * FROM perl_record_set(); SELECT * FROM perl_record_set();
SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text); SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
--
-- Check behavior with erroneous return values
--
CREATE TYPE footype AS (x INTEGER, y INTEGER);
CREATE OR REPLACE FUNCTION foo_good() RETURNS SETOF footype AS $$
return [
{x => 1, y => 2},
{x => 3, y => 4}
];
$$ LANGUAGE plperl;
SELECT * FROM foo_good();
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return {y => 3, z => 4};
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return 42;
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
return [
[1, 2],
[3, 4]
];
$$ LANGUAGE plperl;
SELECT * FROM foo_bad();
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return 42;
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return {y => 3, z => 4};
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return [
[1, 2],
[3, 4]
];
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
return [
{y => 3, z => 4}
];
$$ LANGUAGE plperl;
SELECT * FROM foo_set_bad();
--
-- Check passing a tuple argument
--
CREATE OR REPLACE FUNCTION perl_get_field(footype, text) RETURNS integer AS $$
return $_[0]->{$_[1]};
$$ LANGUAGE plperl;
SELECT perl_get_field((11,12), 'x');
SELECT perl_get_field((11,12), 'y');
SELECT perl_get_field((11,12), 'z');
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment