/********************************************************************** *Theinformationwecacheaboutloadedprocedures * *Thefn_refcountfieldcountsthestruct'sreferencefromthehashtable *shownbelow,plusonereferenceforeachfunctioncalllevelthatisusing *thestruct.Wecanreleasethestruct,andtheassociatedPerlsub,when *thefn_refcountgoestozero.Releasingthestructitselfisdoneby *deletingthefn_cxt,whichalsogetsridofallsubsidiarydata.
**********************************************************************/ typedefstruct plperl_proc_desc
{ char *proname; /* user name of procedure */
MemoryContext fn_cxt; /* memory context for this procedure */ unsignedlong fn_refcount; /* number of active references */
TransactionId fn_xmin; /* xmin/TID of procedure's pg_proc tuple */
ItemPointerData fn_tid;
SV *reference; /* CODE reference for Perl sub */
plperl_interp_desc *interp; /* interpreter it's created in */ bool fn_readonly; /* is function readonly (not volatile)? */
Oid lang_oid;
List *trftypes; bool lanpltrusted; /* is it plperl, rather than plperlu? */ bool fn_retistuple; /* true, if function returns tuple */ bool fn_retisset; /* true, if function returns set */ bool fn_retisarray; /* true if function returns array */ /* Conversion info for function's result type: */
Oid result_oid; /* Oid of result type */
FmgrInfo result_in_func; /* I/O function and arg for result type */
Oid result_typioparam; /* Per-argument info for function's argument types: */ int nargs;
FmgrInfo *arg_out_func; /* output fns for arg types */ bool *arg_is_rowtype; /* is each arg composite? */
Oid *arg_arraytype; /* InvalidOid if not an array */
} plperl_proc_desc;
/********************************************************************** *Forspeedylookup,wemaintainahashtablemappingfrom *functionOID+triggerflag+userOIDtoplperl_proc_descpointers. *Thereasontheplperl_proc_descstructisn'tdirectlypartofthehash *entryistosimplifyrecoveryfromerrorsduringcompile_plperl_function. * *Note:ifthesamefunctioniscalledbymultipleuserIDswithinasession, *therewillbeaseparateplperl_proc_descentryforeachuserIDinthecase *ofplperlfunctions,butonlyoneentryforplperlufunctions,becausewe *setuser_id=0forthatcase.Iftheuserredeclaresthesamefunction *fromplperltoplperluorviceversa,theremightbemultiple *plperl_proc_ptrentriesinthehashtable,butonlyoneisvalid.
**********************************************************************/ typedefstruct plperl_proc_key
{
Oid proc_id; /* Function OID */
/* *is_triggerisreallyabool,butdeclareasOidtoensurethisstruct *containsnopadding
*/
Oid is_trigger; /* is it a trigger function? */
Oid user_id; /* User calling the function, or 0 */
} plperl_proc_key;
/********************************************************************** *InformationforPostgreSQL-Perlarrayconversion.
**********************************************************************/ typedefstruct plperl_array_info
{ int ndims; bool elem_is_rowtype; /* 't' if element type is a rowtype */
Datum *elements; bool *nulls; int *nelems;
FmgrInfo proc;
FmgrInfo transform_proc;
} plperl_array_info;
static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); staticvoid plperl_event_trigger_handler(PG_FUNCTION_ARGS);
/* *Initializeplperl'sGUCs.
*/
DefineCustomBoolVariable("plperl.use_strict",
gettext_noop("If true, trusted and untrusted Perl code will be compiled in strict mode."),
NULL,
&plperl_use_strict, false,
PGC_USERSET, 0,
NULL, NULL, NULL);
/* *plperl.on_initismarkedPGC_SIGHUPtosupporttheideathatitmight *beexecutedinthepostmaster(ifplperlisloadedintothepostmaster *viashared_preload_libraries).Thisisn'treallyrighteitherway, *though.
*/
DefineCustomStringVariable("plperl.on_init",
gettext_noop("Perl initialization code to execute when a Perl interpreter is initialized."),
NULL,
&plperl_on_init,
NULL,
PGC_SIGHUP, 0,
NULL, NULL, NULL);
/* *plperl.on_plperl_initismarkedPGC_SUSETtoavoidissueswherebya *userwhomightnotevenhaveUSAGEprivilegeontheplperllanguage *couldnonethelessuseSETplperl.on_plperl_init='...'toinfluencethe *behaviourofanyexistingplperlfunctionthattheycanexecute(which *mightbeSECURITYDEFINER,leadingtoaprivilegeescalation).See *http://archives.postgresql.org/pgsql-hackers/2010-02/msg00281.php and *theoverallthread. * *Notethatbecauseplperl.use_strictisUSERSET,anefarioususercould *setittobeappliedagainstotherpeople'sfunctions.Thisisjudged *OKsincetheworstresultwouldbeanerror.Yourcodeoughtapass *use_strictanyway;-)
*/
DefineCustomStringVariable("plperl.on_plperl_init",
gettext_noop("Perl initialization code to execute once when plperl is first used."),
NULL,
&plperl_on_plperl_init,
NULL,
PGC_SUSET, 0,
NULL, NULL, NULL);
DefineCustomStringVariable("plperl.on_plperlu_init",
gettext_noop("Perl initialization code to execute once when plperlu is first used."),
NULL,
&plperl_on_plperlu_init,
NULL,
PGC_SUSET, 0,
NULL, NULL, NULL);
/* *Quickexitifalreadyhaveaninterpreter
*/ if (interp_desc->interp)
{
activate_interpreter(interp_desc); return;
}
/* *adoptheldinterpiffree,elsecreatenewoneifpossible
*/ if (plperl_held_interp != NULL)
{ /* first actual use of a perl interpreter */
interp = plperl_held_interp;
/* *Executeplperl.on_plperl_initinthelocked-downinterpreter
*/ if (plperl_on_plperl_init && *plperl_on_plperl_init)
{
eval_pv(plperl_on_plperl_init, FALSE); /* XXX need to find a way to determine a better errcode here */ if (SvTRUE(ERRSV))
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
errcontext("while executing plperl.on_plperl_init")));
}
}
/* convert a hash reference to a datum */ static Datum
plperl_hash_to_datum(SV *src, TupleDesc td)
{
HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), td);
elog(ERROR, "could not get array reference from PostgreSQL::InServer::ARRAY object");
}
} return NULL;
}
/* *helperfunctionforplperl_array_to_datum,recursesformulti-Darrays * *TheArrayBuildStateiscreatedonlywhenwefirstfindascalarelement; *ifwedidn'tdoitlikethat,we'dneedsomeotherconventionforknowing *whetherwe'dalreadyfoundanyscalars(andthusthenumberofdimensions *isfrozen).
*/ staticvoid
array_to_datum_internal(AV *av, ArrayBuildState **astatep, int *ndims, int *dims, int cur_depth,
Oid elemtypid, int32 typmod,
FmgrInfo *finfo, Oid typioparam)
{
dTHX; int i; int len = av_len(av) + 1;
for (i = 0; i < len; i++)
{ /* fetch the array element */
SV **svp = av_fetch(av, i, FALSE);
/* see if this element is an array, if so get that */
SV *sav = svp ? get_perl_array_ref(*svp) : NULL;
/* multi-dimensional array? */ if (sav)
{
AV *nav = (AV *) SvRV(sav);
/* set size when at first element in this level, else compare */ if (i == 0 && *ndims == cur_depth)
{ /* array after some scalars at same level? */ if (*astatep != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("multidimensional arrays must have array expressions with matching dimensions"))); /* too many dimensions? */ if (cur_depth + 1 > MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of array dimensions exceeds the maximum allowed (%d)",
MAXDIM))); /* OK, add a dimension */
dims[*ndims] = av_len(nav) + 1;
(*ndims)++;
} elseif (cur_depth >= *ndims ||
av_len(nav) + 1 != dims[cur_depth])
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
/* recurse to fetch elements of this sub-array */
array_to_datum_internal(nav, astatep,
ndims, dims, cur_depth + 1,
elemtypid, typmod,
finfo, typioparam);
} else
{
Datum dat; bool isnull;
/* scalar after some sub-arrays at same level? */ if (*ndims != cur_depth)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("multidimensional arrays must have array expressions with matching dimensions")));
/* Create ArrayBuildState if we didn't already */ if (*astatep == NULL)
*astatep = initArrayResult(elemtypid,
CurrentMemoryContext, true);
/* ... and save the element value in it */
(void) accumArrayResult(*astatep, dat, isnull,
elemtypid, CurrentMemoryContext);
}
}
}
/* *convertperlarrayreftoadatum
*/ static Datum
plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
{
dTHX;
AV *nav = (AV *) SvRV(src);
ArrayBuildState *astate = NULL;
Oid elemtypid;
FmgrInfo finfo;
Oid typioparam; int dims[MAXDIM]; int lbs[MAXDIM]; int ndims = 1; int i;
elemtypid = get_element_type(typid); if (!elemtypid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot convert Perl array to non-array type %s",
format_type_be(typid))));
/* Get the information needed to convert data to the specified PG type */ staticvoid
_sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam)
{
Oid typinput;
/* XXX would be better to cache these lookups */
getTypeInputInfo(typid,
&typinput, typioparam);
fmgr_info(typinput, finfo);
}
/* *convertPerlSVtoPGdatumoftypetypid,typmodtypmod * *PassthePL/Perlfunction'sfcinfowhenattemptingtoconverttothe *function'sresulttype;otherwisepassNULL.Thisisusedwhenweneedto *resolvetheactualresulttypeofafunctionreturningRECORD. * *finfoandtypioparamshouldbetheresultsof_sv_to_datum_finfoforthe *giventypid,orNULL/InvalidOidtoletthisfunctiondothelookups. * **isnullisanoutputparameter.
*/ static Datum
plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
FunctionCallInfo fcinfo,
FmgrInfo *finfo, Oid typioparam, bool *isnull)
{
FmgrInfo tmp;
Oid funcid;
/* we might recurse */
check_stack_depth();
*isnull = false;
/* *ReturnNULLifresultisundef,orifwe'reinafunctionreturning *VOID.Inthelattercase,weshouldpaynoattentiontothelastPerl *statement'sresult,andthisisaconvenientmeanstoensurethat.
*/ if (!sv || !SvOK(sv) || typid == VOIDOID)
{ /* look up type info if they did not pass it */ if (!finfo)
{
_sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp;
}
*isnull = true; /* must call typinput in case it wants to reject NULL */ return InputFunctionCall(finfo, NULL, typioparam, typmod);
} elseif ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes))) return OidFunctionCall1(funcid, PointerGetDatum(sv)); elseif (SvROK(sv))
{ /* handle references */
SV *sav = get_perl_array_ref(sv);
if (sav)
{ /* handle an arrayref */ return plperl_array_to_datum(sav, typid, typmod);
} elseif (SvTYPE(SvRV(sv)) == SVt_PVHV)
{ /* handle a hashref */
Datum ret;
TupleDesc td; bool isdomain;
if (!type_is_rowtype(typid))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot convert Perl hash to non-composite type %s",
format_type_be(typid))));
td = lookup_rowtype_tupdesc_domain(typid, typmod, true); if (td != NULL)
{ /* Did we look through a domain? */
isdomain = (typid != td->tdtypeid);
} else
{ /* Must be RECORD, try to resolve based on call info */
TypeFuncClass funcclass;
if (fcinfo)
funcclass = get_call_result_type(fcinfo, &typid, &td); else
funcclass = TYPEFUNC_OTHER; if (funcclass != TYPEFUNC_COMPOSITE &&
funcclass != TYPEFUNC_COMPOSITE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context " "that cannot accept type record")));
Assert(td);
isdomain = (funcclass == TYPEFUNC_COMPOSITE_DOMAIN);
}
ret = plperl_hash_to_datum(sv, td);
if (isdomain)
domain_check(ret, false, typid, NULL, NULL);
/* Release on the result of get_call_result_type is harmless */
ReleaseTupleDesc(td);
/* did not pass in any typeinfo? look it up */ if (!finfo)
{
_sv_to_datum_finfo(typid, &tmp, &typioparam);
finfo = &tmp;
}
ret = InputFunctionCall(finfo, str, typioparam, typmod);
pfree(str);
return ret;
}
}
/* Convert the perl SV to a string returned by the type output function */ char *
plperl_sv_to_literal(SV *sv, char *fqtypename)
{
Oid typid;
Oid typoutput;
Datum datum; bool typisvarlena,
isnull;
check_spi_usage_allowed();
typid = DirectFunctionCall1(regtypein, CStringGetDatum(fqtypename)); if (!OidIsValid(typid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("lookup failed for type %s", fqtypename)));
datum = plperl_sv_to_datum(sv,
typid, -1,
NULL, NULL, InvalidOid,
&isnull);
/* *ConvertPostgreSQLarraydatumtoaperlarrayreference. * *typidisarg'sOID,whichmustbeanarraytype.
*/ static SV *
plperl_ref_from_pg_array(Datum arg, Oid typid)
{
dTHX;
ArrayType *ar = DatumGetArrayTypeP(arg);
Oid elementtype = ARR_ELEMTYPE(ar);
int16 typlen; bool typbyval; char typalign,
typdelim;
Oid typioparam;
Oid typoutputfunc;
Oid transform_funcid; int i,
nitems,
*dims;
plperl_array_info *info;
SV *av;
HV *hv;
/* *Currentlywemakenoefforttocacheanyofthestuffwelookuphere, *whichisbad.
*/
info = palloc0(sizeof(plperl_array_info));
/* get element type information, including output conversion function */
get_type_io_data(elementtype, IOFunc_output,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam, &typoutputfunc);
/* Check for a transform function */
transform_funcid = get_transform_fromsql(elementtype,
current_call_data->prodesc->lang_oid,
current_call_data->prodesc->trftypes);
/* Look up transform or output function as appropriate */ if (OidIsValid(transform_funcid))
fmgr_info(transform_funcid, &info->transform_proc); else
fmgr_info(typoutputfunc, &info->proc);
/* Get the number and bounds of array dimensions */
info->ndims = ARR_NDIM(ar);
dims = ARR_DIMS(ar);
/* No dimensions? Return an empty array */ if (info->ndims == 0)
{
av = newRV_noinc((SV *) newAV());
} else
{
deconstruct_array(ar, elementtype, typlen, typbyval,
typalign, &info->elements, &info->nulls,
&nitems);
/* Get total number of elements in each dimension */
info->nelems = palloc(sizeof(int) * info->ndims);
info->nelems[0] = nitems; for (i = 1; i < info->ndims; i++)
info->nelems[i] = info->nelems[i - 1] / dims[i - 1];
/* *Recursivelyformarrayreferencesfromsplicesoftheinitialarray
*/ static SV *
split_array(plperl_array_info *info, int first, int last, int nest)
{
dTHX; int i;
AV *result;
/* we should only be called when we have something to split */
Assert(info->ndims > 0);
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
result = newAV(); for (i = first; i < last; i += info->nelems[nest + 1])
{ /* Recursively form references to arrays of lower dimensions */
SV *ref = split_array(info, i, i + info->nelems[nest + 1], nest + 1);
/* *CreateaPerlreferencefromaone-dimensionalCarray,converting *compositetypeelementstohashreferences.
*/ static SV *
make_array_ref(plperl_array_info *info, int first, int last)
{
dTHX; int i;
AV *result = newAV();
for (i = first; i < last; i++)
{ if (info->nulls[i])
{ /* *Wecan'tuse&PL_sv_undefhere.See"AVs,HVsandundefined *values"inperlguts.
*/
av_push(result, newSV(0));
} else
{
Datum itemvalue = info->elements[i];
if (info->transform_proc.fn_oid)
av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue))); elseif (info->elem_is_rowtype) /* Handle composite type elements */
av_push(result, plperl_hash_from_datum(itemvalue)); else
{ char *val = OutputFunctionCall(&info->proc, itemvalue);
if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
when = "BEFORE"; elseif (TRIGGER_FIRED_AFTER(tdata->tg_event))
when = "AFTER"; elseif (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
when = "INSTEAD OF"; else
when = "UNKNOWN";
hv_store_string(hv, "when", cstr2sv(when));
/* Set up the arguments for an event trigger call. */ static SV *
plperl_event_trigger_build_args(FunctionCallInfo fcinfo)
{
dTHX;
EventTriggerData *tdata;
HV *hv;
/* Construct the modified new tuple to be returned from a trigger. */ static HeapTuple
plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
{
dTHX;
SV **svp;
HV *hvNew;
HE *he;
HeapTuple rtup;
TupleDesc tupdesc; int natts;
Datum *modvalues; bool *modnulls; bool *modrepls;
svp = hv_fetch_string(hvTD, "new"); if (!svp)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("$_TD->{new} does not exist"))); if (!SvOK(*svp) || !SvROK(*svp) || SvTYPE(SvRV(*svp)) != SVt_PVHV)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("$_TD->{new} is not a hash reference")));
hvNew = (HV *) SvRV(*svp);
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 is_trigger = false; bool is_event_trigger = false; int i;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
/* Get the new function's pg_proc entry */
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); 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, EVTTRIGGER, RECORD, or VOID */ if (functyptype == TYPTYPE_PSEUDO)
{ if (proc->prorettype == TRIGGEROID)
is_trigger = true; elseif (proc->prorettype == EVENT_TRIGGEROID)
is_event_trigger = true; elseif (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Perl 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]) == TYPTYPE_PSEUDO &&
argtypes[i] != RECORDOID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Perl functions cannot accept type %s",
format_type_be(argtypes[i]))));
}
ReleaseSysCache(tuple);
/* Postpone body checks if !check_function_bodies */ if (check_function_bodies)
{
(void) compile_plperl_function(funcoid, is_trigger, is_event_trigger);
}
/* the result of a validator is ignored */
PG_RETURN_VOID();
}
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
newXS("PostgreSQL::InServer::Util::bootstrap",
boot_PostgreSQL__InServer__Util, file); /* newXS for...::SPI::bootstrap is in select_perl_context() */
}
static SV *
plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
{
dTHX;
dSP;
SV *retval; int i; int count;
Oid *argtypes = NULL; int nargs = 0;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(sp, desc->nargs);
/* Get signature for true functions; inline blocks have no args. */ if (fcinfo->flinfo->fn_oid)
get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs);
Assert(nargs == desc->nargs);
for (i = 0; i < desc->nargs; i++)
{ if (fcinfo->args[i].isnull)
PUSHs(&PL_sv_undef); elseif (desc->arg_is_rowtype[i])
{
SV *sv = plperl_hash_from_datum(fcinfo->args[i].value);
PUSHs(sv_2mortal(sv));
} else
{
SV *sv;
Oid funcid;
/* Do NOT use G_KEEPERR here */
count = call_sv(desc->reference, G_SCALAR | G_EVAL);
SPAGAIN;
if (count != 1)
{
PUTBACK;
FREETMPS;
LEAVE;
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("didn't get a return item from function")));
}
if (SvTRUE(ERRSV))
{
(void) POPs;
PUTBACK;
FREETMPS;
LEAVE; /* XXX need to find a way to determine a better errcode here */
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
}
save_item(TDsv); /* local $_TD */
sv_setsv(TDsv, td);
PUSHMARK(sp);
EXTEND(sp, tg_trigger->tgnargs);
for (i = 0; i < tg_trigger->tgnargs; i++)
PUSHs(sv_2mortal(cstr2sv(tg_trigger->tgargs[i])));
PUTBACK;
/* Do NOT use G_KEEPERR here */
count = call_sv(desc->reference, G_SCALAR | G_EVAL);
SPAGAIN;
if (count != 1)
{
PUTBACK;
FREETMPS;
LEAVE;
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("didn't get a return item from trigger function")));
}
if (SvTRUE(ERRSV))
{
(void) POPs;
PUTBACK;
FREETMPS;
LEAVE; /* XXX need to find a way to determine a better errcode here */
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
}
save_item(TDsv); /* local $_TD */
sv_setsv(TDsv, td);
PUSHMARK(sp);
PUTBACK;
/* Do NOT use G_KEEPERR here */
count = call_sv(desc->reference, G_SCALAR | G_EVAL);
SPAGAIN;
if (count != 1)
{
PUTBACK;
FREETMPS;
LEAVE;
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("didn't get a return item from trigger function")));
}
if (SvTRUE(ERRSV))
{
(void) POPs;
PUTBACK;
FREETMPS;
LEAVE; /* XXX need to find a way to determine a better errcode here */
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
}
/* Set a callback for error reporting */
pl_error_context.callback = plperl_exec_callback;
pl_error_context.previous = error_context_stack;
pl_error_context.arg = prodesc->proname;
error_context_stack = &pl_error_context;
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (prodesc->fn_retisset)
{ /* Check context before allowing the call to go through */ if (!rsi || !IsA(rsi, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsi->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialize mode required, but it is not allowed in this context")));
}
/* *IfthePerlfunctionreturnedanarrayref,wepretendthatit *calledreturn_next()foreachelementofthearray,tohandleold *SRFsthatdidn'tknowaboutreturn_next().Anyothersortofreturn *valueisanerror,exceptundefwhichmeansreturnanemptyset.
*/
sav = get_perl_array_ref(perlret); if (sav)
{
dTHX; int i = 0;
SV **svp = 0;
AV *rav = (AV *) SvRV(sav);
while ((svp = av_fetch(rav, i, FALSE)) != NULL)
{
plperl_return_next_internal(*svp);
i++;
}
} elseif (SvOK(perlret))
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("set-returning PL/Perl function must return " "reference to array or use return_next")));
}
if (perlret == NULL || !SvOK(perlret))
{ /* undef result means go ahead with original tuple */
TriggerData *trigdata = ((TriggerData *) fcinfo->context);
/* Otherwise, unlink the obsoleted entry from the hashtable ... */
proc_ptr->proc_ptr = NULL; /* ... and release the corresponding refcount, probably deleting it */
decrement_prodesc_refcount(prodesc);
}
returnfalse;
}
staticvoid
free_plperl_function(plperl_proc_desc *prodesc)
{
Assert(prodesc->fn_refcount == 0); /* Release CODE reference, if we have one, from the appropriate interp */ if (prodesc->reference)
{
plperl_interp_desc *oldinterp = plperl_active_interp;
activate_interpreter(prodesc->interp);
SvREFCNT_dec_current(prodesc->reference);
activate_interpreter(oldinterp);
} /* Release all PG-owned data for this proc */
MemoryContextDelete(prodesc->fn_cxt);
}
/* We'll need the pg_proc tuple in any case... */
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); if (!HeapTupleIsValid(procTup))
elog(ERROR, "cache lookup failed for function %u", fn_oid);
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
/* Build a hash from a given composite/row datum */ static SV *
plperl_hash_from_datum(Datum attr)
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
SV *sv;
td = DatumGetHeapTupleHeader(attr);
/* 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;
/* Build a hash from all attributes of a given tuple. */ static SV *
plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated)
{
dTHX;
HV *hv; int i;
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
hv = newHV();
hv_ksplit(hv, tupdesc->natts); /* pre-grow the hash */
for (i = 0; i < tupdesc->natts; i++)
{
Datum attr; bool isnull,
typisvarlena; char *attname;
Oid typoutput;
Form_pg_attribute att = TupleDescAttr(tupdesc, i);
if (att->attisdropped) continue;
if (att->attgenerated)
{ /* don't include unless requested */ if (!include_generated) continue; /* never include virtual columns */ if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) continue;
}
staticvoid
check_spi_usage_allowed(void)
{ /* see comment in plperl_fini() */ if (plperl_ending)
{ /* simple croak as we don't want to involve PostgreSQL code */
croak("SPI functions can not be used in END blocks");
}
/* *DisallowSPIusageifwe'renotexecutingafully-compiledplperl *function.Itmightseemimpossibletogethereinthatcase,butthere *arecaseswherePerlwilltrytoexecutecodeduringcompilation.If *weproceedwearelikelytocrashtryingtodereferencetheprodesc *pointer.Workingaroundthatmightbepossible,butitseemsunwise *becauseit'dallowcodeexecutiontohappenwhilevalidatinga *function,whichisundesirable.
*/ if (current_call_data == NULL || current_call_data->prodesc == NULL)
{ /* simple croak as we don't want to involve PostgreSQL code */
croak("SPI functions can not be used during function compilation");
}
}
HV *
plperl_spi_exec(char *query, int limit)
{
HV *ret_hv;
if (status > 0 && tuptable)
{
AV *rows;
SV *row;
uint64 i;
/* Prevent overflow in call to av_extend() */ if (processed > (uint64) AV_SIZE_MAX)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("query result has too many rows to fit in a Perl array")));
rows = newAV();
av_extend(rows, processed); for (i = 0; i < processed; i++)
{
row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc, true);
av_push(rows, row);
}
hv_store_string(result, "rows",
newRV_noinc((SV *) rows));
}
if (!prodesc->fn_retisset)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use return_next in a non-SETOF function")));
if (!current_call_data->ret_tdesc)
{
TupleDesc tupdesc;
Assert(!current_call_data->tuple_store);
/* *Thisisthefirstcalltoreturn_nextinthecurrentPL/Perl *functioncall,soidentifytheoutputtupletypeandcreatea *tuplestoretoholdtheresultrows.
*/ if (prodesc->fn_retistuple)
{
TypeFuncClass funcclass;
Oid typid;
funcclass = get_call_result_type(fcinfo, &typid, &tupdesc); if (funcclass != TYPEFUNC_COMPOSITE &&
funcclass != TYPEFUNC_COMPOSITE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context " "that cannot accept type record"))); /* if domain-over-composite, remember the domain's type OID */ if (funcclass == TYPEFUNC_COMPOSITE_DOMAIN)
current_call_data->cdomain_oid = typid;
} else
{
tupdesc = rsi->expectedDesc; /* Protect assumption below that we return exactly one column */ if (tupdesc == NULL || tupdesc->natts != 1)
elog(ERROR, "expected single-column result descriptor for non-composite SETOF result");
}
BeginInternalSubTransaction(NULL); /* Want to run inside function's memory context */
MemoryContextSwitchTo(oldcontext);
PG_TRY();
{
SPIPlanPtr plan;
Portal portal;
/* Make sure the query is validly encoded */
pg_verifymbstr(query, strlen(query), false);
/* Create a cursor for the query */
plan = SPI_prepare(query, 0, NULL); if (plan == NULL)
elog(ERROR, "SPI_prepare() failed:%s",
SPI_result_code_string(SPI_result));
/* Save error info */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/* Drop anything we managed to allocate */ if (hash_entry)
hash_search(plperl_active_interp->query_hash,
qdesc->qname,
HASH_REMOVE, NULL); if (plan_cxt)
MemoryContextDelete(plan_cxt); if (plan)
SPI_freeplan(plan);
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.121Bemerkung:
(vorverarbeitet am 2026-08-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.