Hi, plperl_call_perl_func() invokes the user's sub with call_sv(G_EVAL). A die inside the sub becomes ERRSV and we report it as ERROR.
After the sub returns we convert the result in C. That path can dispatch Perl magic: SvGETMAGIC(), av_fetch(), hv_iternext(), and transform functions such as plperl_to_hstore(). A croak from FETCH then finds no CXt_EVAL on Perl's cx stack. die_where() treats it as uncaught, writes the message to stderr, and my_exit_jump() does JMPENV_JUMP(2). The backend exits with status 2. The postmaster runs crash recovery. A tied scalar that RETURNS text already becomes ERROR, because return $s runs FETCH inside pp_leavesub while G_EVAL is still active. return \@tied or \%tied only returns a reference. The element FETCHes happen later, after call_sv has returned. The hole is any Perl croak from that C conversion, not only tie. A trigger that returns an overloaded object dies in sv2cstr(). JMPENV_PUSH around the handler is not an eval context. It would catch JMPENV_JUMP(2) after Perl has already unwound its stacks. The interpreter is then in exit state, so the honest mapping is FATAL, not ERROR. The conversion needs a live Perl eval context. Register PostgreSQL::InServer::_eval with newXS, next to SPI::bootstrap. After SPI_finish, plperl_func_handler and plperl_trigger_handler call that XSUB with call_sv(G_VOID | G_EVAL). The XSUB runs the existing conversion. Postgres errors are turned into croak_cstr(), the same pattern as plperl_spi_exec(). After call_sv returns we restore PG_exception_stack and error_context_stack, because a croak from FETCH longjmps to call_sv and skips PG_TRY inside the XSUB. Nested conversion from return_next() is already under the user's eval, so the trampoline just calls the C function. A croak from FETCH is then JMPENV_JUMP(3). call_sv returns with ERRSV set. We report ERROR. The interpreter stays usable. The same session can run another PL/Perl function. A regress case is included. It covers die in FETCH, a missing FETCH method, and die in FIRSTKEY. A plperlu case covers croak from overload stringify on a trigger return. -- Regards, Rachitskiy Andrey
From 12567e70e6563d441485d290e74331594603d58f Mon Sep 17 00:00:00 2001 From: Andrey Rachitskiy <[email protected]> Date: Wed, 16 Sep 2026 22:30:44 +0500 Subject: [PATCH] Catch croak during PL/Perl result conversion call_sv(G_EVAL) protects the user's sub. Result conversion after SPI_finish runs in C. That path can run Perl: SvGETMAGIC, av_fetch, hv_iternext, sv2cstr, and transforms. A croak there has no eval frame, so Perl treats it as uncaught and exits the backend. Run the conversion through an XSUB invoked with G_EVAL. Postgres errors become croak_cstr, matching SPI. Restore PG_exception_stack after call_sv because a Perl croak skips the XSUB's PG_TRY. Author: Andrey Rachitskiy <[email protected]> --- src/pl/plperl/expected/plperl_tied.out | 41 +++ src/pl/plperl/expected/plperlu.out | 12 + src/pl/plperl/plperl.c | 430 ++++++++++++++++++------- src/pl/plperl/sql/plperl_tied.sql | 40 +++ src/pl/plperl/sql/plperlu.sql | 13 + 5 files changed, 425 insertions(+), 111 deletions(-) diff --git a/src/pl/plperl/expected/plperl_tied.out b/src/pl/plperl/expected/plperl_tied.out index 6d85dd855f2..c29368e19e9 100644 --- a/src/pl/plperl/expected/plperl_tied.out +++ b/src/pl/plperl/expected/plperl_tied.out @@ -130,3 +130,44 @@ SELECT i, v FROM tied_trigger_test; 1 | from_tie (1 row) +-- die from FETCH after the sub returns (no user-level eval context). +-- Without an eval around conversion this used to exit the backend. +CREATE FUNCTION tied_array_fetch_die() RETURNS int[] AS $$ + package TiedArrayFetchDie; + sub TIEARRAY { bless [], $_[0] } + sub FETCHSIZE { 1 } + sub FETCH { die 'tied array FETCH died' } + my @a; + tie @a, 'TiedArrayFetchDie'; + return \@a; +$$ LANGUAGE plperl; +SELECT tied_array_fetch_die(); +ERROR: tied array FETCH died at line 5. +CONTEXT: PL/Perl function "tied_array_fetch_die" +CREATE FUNCTION tied_array_no_fetch() RETURNS int[] AS $$ + package TiedArrayNoFetch; + sub TIEARRAY { bless [], $_[0] } + sub FETCHSIZE { 1 } + my @a; + tie @a, 'TiedArrayNoFetch'; + return \@a; +$$ LANGUAGE plperl; +SELECT tied_array_no_fetch(); +ERROR: Can't locate object method "FETCH" via package "TiedArrayNoFetch". +CONTEXT: PL/Perl function "tied_array_no_fetch" +CREATE FUNCTION tied_hash_firstkey_die() RETURNS tiedrowperl AS $$ + package TiedHashFirstkeyDie; + sub TIEHASH { bless {}, $_[0] } + sub STORE { $_[0]{$_[1]} = $_[2] } + sub FIRSTKEY { die 'tied hash FIRSTKEY died' } + sub NEXTKEY { each %{$_[0]} } + sub FETCH { $_[0]{$_[1]} } + my %h; + tie %h, 'TiedHashFirstkeyDie'; + $h{f1} = 1; + return \%h; +$$ LANGUAGE plperl; +SELECT tied_hash_firstkey_die(); +ERROR: tied hash FIRSTKEY died at line 5. +CONTEXT: PL/Perl function "tied_hash_firstkey_die" + diff --git a/src/pl/plperl/expected/plperlu.out b/src/pl/plperl/expected/plperlu.out index a3edb384971..d306de12ebf 100644 --- a/src/pl/plperl/expected/plperlu.out +++ b/src/pl/plperl/expected/plperlu.out @@ -13,3 +13,15 @@ WARNING: 42 at line 1. CREATE OR REPLACE FUNCTION perl_unicode_regex(text) RETURNS INTEGER AS $$ return ($_[0] =~ /\x{263A}|happy/i) ? 1 : 0; # unicode smiley $$ LANGUAGE plperlu; + +-- croak from overload stringify after the sub returns. +CREATE TABLE overload_trig_test (); +CREATE FUNCTION overload_trig_stringify_die() RETURNS trigger AS $$ + use overload '""' => sub { die 'overload stringify died' }; + bless {}; +$$ LANGUAGE plperlu; +CREATE TRIGGER overload_trig_stringify_die_trig BEFORE INSERT ON overload_trig_test +EXECUTE PROCEDURE overload_trig_stringify_die(); +INSERT INTO overload_trig_test DEFAULT VALUES; +ERROR: overload stringify died at line 2. +CONTEXT: PL/Perl function "overload_trig_stringify_die" diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index a59dbc94372..2e0db303ce7 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -88,6 +88,7 @@ typedef struct plperl_interp_desc Oid user_id; /* Hash key (must be first!) */ PerlInterpreter *interp; /* The interpreter */ HTAB *query_hash; /* plperl_query_entry structs */ + CV *eval_cv; /* _eval, called with G_EVAL */ } plperl_interp_desc; @@ -181,8 +182,29 @@ typedef struct plperl_call_data Oid cdomain_oid; /* 0 unless returning domain-over-composite */ void *cdomain_info; MemoryContext tmp_cxt; + /* Conversion of the Perl result, run inside the _eval XSUB */ + Datum (*eval_fn) (void *arg); + void *eval_arg; + Datum eval_result; /* G_VOID XSUB has no Datum return */ } plperl_call_data; +/* Argument of plperl_convert_func_result. */ +typedef struct +{ + plperl_proc_desc *prodesc; + FunctionCallInfo fcinfo; + ReturnSetInfo *rsi; /* fcinfo->resultinfo, may be NULL */ + SV *perlret; +} plperl_convert_func_arg; + +/* Argument of plperl_convert_trigger_result. */ +typedef struct +{ + FunctionCallInfo fcinfo; + SV *perlret; + HV *hvTD; /* $_TD, used if the trigger returns MODIFY */ +} plperl_convert_trig_arg; + /********************************************************************** * The information we cache about prepared and saved plans **********************************************************************/ @@ -300,6 +322,10 @@ static void plperl_inline_callback(void *arg); static char *strip_trailing_ws(const char *msg); static OP *pp_require_safe(pTHX); static void activate_interpreter(plperl_interp_desc *interp_desc); +static void plperl_xs_eval(pTHX_ CV *cv); +static Datum plperl_run_under_eval(Datum (*fn) (void *arg), void *arg); +static Datum plperl_convert_func_result(void *arg); +static Datum plperl_convert_trigger_result(void *arg); #if defined(WIN32) && PERL_VERSION_LT(5, 28, 0) static char *setlocale_perl(int category, char *locale); @@ -522,6 +548,7 @@ select_perl_context(bool trusted) /* Initialize newly-created hashtable entry */ interp_desc->interp = NULL; interp_desc->query_hash = NULL; + interp_desc->eval_cv = NULL; } /* Make sure we have a query_hash for this interpreter */ @@ -616,6 +643,10 @@ select_perl_context(bool trusted) (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))), errcontext("while executing PostgreSQL::InServer::SPI::bootstrap"))); + + interp_desc->eval_cv = + newXS("PostgreSQL::InServer::_eval", + plperl_xs_eval, __FILE__); } /* Fully initialized, so mark the hashtable entry valid */ @@ -2396,6 +2427,7 @@ plperl_func_handler(PG_FUNCTION_ARGS) Datum retval = 0; ReturnSetInfo *rsi; ErrorContextCallback pl_error_context; + plperl_convert_func_arg carg; nonatomic = fcinfo->context && IsA(fcinfo->context, CallContext) && @@ -2442,61 +2474,11 @@ plperl_func_handler(PG_FUNCTION_ARGS) if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish() failed"); - if (prodesc->fn_retisset) - { - dTHX; - SV *sav; - - /* - * 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, except undef which means return an empty set. - */ - plperl_materialize_sv(perlret); - sav = get_perl_array_ref(perlret); - if (sav) - { - AV *rav = (AV *) SvRV(sav); - Size_t alen = av_count(rav); - - for (Size_t i = 0; i < alen; i++) - { - SV **svp = av_fetch(rav, i, FALSE); - - if (svp) - plperl_return_next_internal(*svp); - } - } - else if (perlret && SvOK(perlret)) - { - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("set-returning PL/Perl function must return " - "reference to array or use return_next"))); - } - - rsi->returnMode = SFRM_Materialize; - if (current_call_data->tuple_store) - { - rsi->setResult = current_call_data->tuple_store; - rsi->setDesc = current_call_data->ret_tdesc; - } - retval = (Datum) 0; - } - else if (prodesc->result_oid) - { - retval = plperl_sv_to_datum(perlret, - prodesc->result_oid, - -1, - fcinfo, - &prodesc->result_in_func, - prodesc->result_typioparam, - &fcinfo->isnull); - - if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo)) - rsi->isDone = ExprEndResult; - } + carg.prodesc = prodesc; + carg.fcinfo = fcinfo; + carg.rsi = rsi; + carg.perlret = perlret; + retval = plperl_run_under_eval(plperl_convert_func_result, &carg); /* Restore the previous error callback */ error_context_stack = pl_error_context.previous; @@ -2519,6 +2501,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ErrorContextCallback pl_error_context; TriggerData *tdata; int rc PG_USED_FOR_ASSERTS_ONLY; + plperl_convert_trig_arg carg; /* Connect to SPI manager */ SPI_connect(); @@ -2554,62 +2537,10 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish() failed"); - plperl_materialize_sv(perlret); - - if (perlret == NULL || !SvOK(perlret)) - { - /* undef result means go ahead with original tuple */ - TriggerData *trigdata = ((TriggerData *) fcinfo->context); - - if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) - retval = PointerGetDatum(trigdata->tg_trigtuple); - else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) - retval = PointerGetDatum(trigdata->tg_newtuple); - else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) - retval = PointerGetDatum(trigdata->tg_trigtuple); - else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) - retval = PointerGetDatum(trigdata->tg_trigtuple); - else - retval = (Datum) 0; /* can this happen? */ - } - else - { - HeapTuple trv; - char *tmp; - - tmp = sv2cstr(perlret); - - if (pg_strcasecmp(tmp, "SKIP") == 0) - trv = NULL; - else if (pg_strcasecmp(tmp, "MODIFY") == 0) - { - TriggerData *trigdata = (TriggerData *) fcinfo->context; - - if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) - trv = plperl_modify_tuple(hvTD, trigdata, - trigdata->tg_trigtuple); - else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) - trv = plperl_modify_tuple(hvTD, trigdata, - trigdata->tg_newtuple); - else - { - ereport(WARNING, - (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), - errmsg("ignoring modified row in DELETE trigger"))); - trv = NULL; - } - } - else - { - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), - errmsg("result of PL/Perl trigger function must be undef, " - "\"SKIP\", or \"MODIFY\""))); - trv = NULL; - } - retval = PointerGetDatum(trv); - pfree(tmp); - } + carg.fcinfo = fcinfo; + carg.perlret = perlret; + carg.hvTD = hvTD; + retval = plperl_run_under_eval(plperl_convert_trigger_result, &carg); /* Restore the previous error callback */ error_context_stack = pl_error_context.previous; @@ -2657,6 +2588,283 @@ plperl_event_trigger_handler(PG_FUNCTION_ARGS) SvREFCNT_dec_current(svTD); } +/* + * Called from plperl_run_under_eval via call_sv(G_EVAL). Runs eval_fn. + * Postgres ERROR becomes croak_cstr, matching SPI. + */ +static void +plperl_xs_eval(pTHX_ CV *cv) +{ + SV **mark; + MemoryContext oldcontext = CurrentMemoryContext; + + (void) cv; + + /* + * PUSHMARK stored an offset from PL_stack_base, not a pointer. + * Peek at the mark so call_sv still has it after we return. + */ + mark = PL_stack_base + *PL_markstack_ptr; + + if (current_call_data == NULL || current_call_data->eval_fn == NULL) + Perl_croak(aTHX_ "PL/Perl eval invoked out of context"); + + PG_TRY(); + { + current_call_data->eval_result = + current_call_data->eval_fn(current_call_data->eval_arg); + } + PG_CATCH(); + { + ErrorData *edata; + + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + croak_cstr(edata->message); + } + PG_END_TRY(); + + PL_stack_sp = mark; /* G_VOID: no Perl results */ +} + +/* + * The user's PL/Perl sub is invoked with call_sv(G_EVAL). A die + * inside the sub becomes ERRSV. After that returns we convert the + * result in C. av_fetch, hv_iternext, and SvGETMAGIC can run Perl + * FETCH. There is no eval frame left, so croak is uncaught and Perl + * exits the backend. This function runs the conversion through an + * XSUB with G_EVAL so croak becomes ERRSV and we report ERROR. + * + * The call sequence is: + * + * +----------------------------+ + * | plperl_run_under_eval | + * +-------------+--------------+ + * | + * v + * +----------------------------+ + * | call_sv(G_VOID | G_EVAL) | + * +-------------+--------------+ + * | + * v + * +----------------------------+ + * | plperl_xs_eval | + * | PG_TRY { fn(arg) } | + * | PG_CATCH { croak_cstr } | + * +----------------------------+ + */ +static Datum +plperl_run_under_eval(Datum (*fn) (void *arg), void *arg) +{ + dTHX; + dSP; + sigjmp_buf *save_ex = PG_exception_stack; + ErrorContextCallback *save_ctx = error_context_stack; + int count; + + Assert(current_call_data != NULL); + + /* + * Already inside the XSUB. Call fn here rather than nesting + * another call_sv. + */ + if (current_call_data->eval_fn != NULL) + return fn(arg); + + if (plperl_active_interp == NULL || plperl_active_interp->eval_cv == NULL) + elog(ERROR, "PL/Perl eval is not initialized"); + + /* + * call_sv cannot pass a C function pointer. The XSUB reads fn and + * arg from current_call_data. Clear eval_fn after call_sv so a Perl + * call to _eval is rejected as out of context. + */ + current_call_data->eval_fn = fn; + current_call_data->eval_arg = arg; + current_call_data->eval_result = (Datum) 0; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + PUTBACK; + /* + * G_EVAL puts a Perl eval frame on the cx stack so croak from + * conversion becomes ERRSV. G_VOID because the XSUB returns nothing. + */ + count = call_sv((SV *) plperl_active_interp->eval_cv, G_VOID | G_EVAL); + (void) count; + SPAGAIN; + PUTBACK; + FREETMPS; + LEAVE; + + current_call_data->eval_fn = NULL; + current_call_data->eval_arg = NULL; + + /* + * croak longjmps to call_sv. The XSUB does not return, so PG_TRY + * never restores PG_exception_stack or error_context_stack. + */ + PG_exception_stack = save_ex; + error_context_stack = save_ctx; + + if (SvTRUE(ERRSV)) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))))); + + return current_call_data->eval_result; +} + +/* + * Convert a PL/Perl function result after SPI_finish. Called via + * plperl_run_under_eval so croak from conversion is caught. + */ +static Datum +plperl_convert_func_result(void *arg) +{ + plperl_convert_func_arg *carg = (plperl_convert_func_arg *) arg; + plperl_proc_desc *prodesc = carg->prodesc; + FunctionCallInfo fcinfo = carg->fcinfo; + ReturnSetInfo *rsi = carg->rsi; + SV *perlret = carg->perlret; + + if (prodesc->fn_retisset) + { + dTHX; + SV *sav; + + /* + * 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, except undef which means return an empty set. + */ + plperl_materialize_sv(perlret); + sav = get_perl_array_ref(perlret); + if (sav) + { + AV *rav = (AV *) SvRV(sav); + Size_t alen = av_count(rav); + + for (Size_t i = 0; i < alen; i++) + { + SV **svp = av_fetch(rav, i, FALSE); + + if (svp) + plperl_return_next_internal(*svp); + } + } + else if (perlret && SvOK(perlret)) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("set-returning PL/Perl function must return " + "reference to array or use return_next"))); + } + + rsi->returnMode = SFRM_Materialize; + if (current_call_data->tuple_store) + { + rsi->setResult = current_call_data->tuple_store; + rsi->setDesc = current_call_data->ret_tdesc; + } + return (Datum) 0; + } + + if (prodesc->result_oid) + { + Datum retval; + + retval = plperl_sv_to_datum(perlret, + prodesc->result_oid, + -1, + fcinfo, + &prodesc->result_in_func, + prodesc->result_typioparam, + &fcinfo->isnull); + + if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo)) + rsi->isDone = ExprEndResult; + return retval; + } + + return (Datum) 0; +} + +/* + * Convert a PL/Perl trigger result after SPI_finish. Called via + * plperl_run_under_eval so croak from conversion is caught. + */ +static Datum +plperl_convert_trigger_result(void *arg) +{ + dTHX; + plperl_convert_trig_arg *carg = (plperl_convert_trig_arg *) arg; + SV *perlret = carg->perlret; + HV *hvTD = carg->hvTD; + Datum retval; + + plperl_materialize_sv(perlret); + + if (perlret == NULL || !SvOK(perlret)) + { + /* undef result means go ahead with original tuple */ + TriggerData *trigdata = ((TriggerData *) carg->fcinfo->context); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + retval = PointerGetDatum(trigdata->tg_trigtuple); + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + retval = PointerGetDatum(trigdata->tg_newtuple); + else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) + retval = PointerGetDatum(trigdata->tg_trigtuple); + else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) + retval = PointerGetDatum(trigdata->tg_trigtuple); + else + retval = (Datum) 0; /* can this happen? */ + } + else + { + HeapTuple trv; + char *tmp; + + tmp = sv2cstr(perlret); + + if (pg_strcasecmp(tmp, "SKIP") == 0) + trv = NULL; + else if (pg_strcasecmp(tmp, "MODIFY") == 0) + { + TriggerData *trigdata = (TriggerData *) carg->fcinfo->context; + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + trv = plperl_modify_tuple(hvTD, trigdata, + trigdata->tg_trigtuple); + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + trv = plperl_modify_tuple(hvTD, trigdata, + trigdata->tg_newtuple); + else + { + ereport(WARNING, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("ignoring modified row in DELETE trigger"))); + trv = NULL; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("result of PL/Perl trigger function must be undef, " + "\"SKIP\", or \"MODIFY\""))); + trv = NULL; + } + retval = PointerGetDatum(trv); + pfree(tmp); + } + + return retval; +} static bool validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) diff --git a/src/pl/plperl/sql/plperl_tied.sql b/src/pl/plperl/sql/plperl_tied.sql index 5f546cc72c3..88d5934d242 100644 --- a/src/pl/plperl/sql/plperl_tied.sql +++ b/src/pl/plperl/sql/plperl_tied.sql @@ -111,3 +111,43 @@ FOR EACH ROW EXECUTE PROCEDURE tied_modify(); INSERT INTO tied_trigger_test (i, v) VALUES (1, 'orig'); SELECT i, v FROM tied_trigger_test; + +-- die from FETCH after the sub returns (no user-level eval context). +-- Without an eval around conversion this used to exit the backend. +CREATE FUNCTION tied_array_fetch_die() RETURNS int[] AS $$ + package TiedArrayFetchDie; + sub TIEARRAY { bless [], $_[0] } + sub FETCHSIZE { 1 } + sub FETCH { die 'tied array FETCH died' } + my @a; + tie @a, 'TiedArrayFetchDie'; + return \@a; +$$ LANGUAGE plperl; + +SELECT tied_array_fetch_die(); + +CREATE FUNCTION tied_array_no_fetch() RETURNS int[] AS $$ + package TiedArrayNoFetch; + sub TIEARRAY { bless [], $_[0] } + sub FETCHSIZE { 1 } + my @a; + tie @a, 'TiedArrayNoFetch'; + return \@a; +$$ LANGUAGE plperl; + +SELECT tied_array_no_fetch(); + +CREATE FUNCTION tied_hash_firstkey_die() RETURNS tiedrowperl AS $$ + package TiedHashFirstkeyDie; + sub TIEHASH { bless {}, $_[0] } + sub STORE { $_[0]{$_[1]} = $_[2] } + sub FIRSTKEY { die 'tied hash FIRSTKEY died' } + sub NEXTKEY { each %{$_[0]} } + sub FETCH { $_[0]{$_[1]} } + my %h; + tie %h, 'TiedHashFirstkeyDie'; + $h{f1} = 1; + return \%h; +$$ LANGUAGE plperl; + +SELECT tied_hash_firstkey_die(); diff --git a/src/pl/plperl/sql/plperlu.sql b/src/pl/plperl/sql/plperlu.sql index be43df5d90a..a153ee05e05 100644 --- a/src/pl/plperl/sql/plperlu.sql +++ b/src/pl/plperl/sql/plperlu.sql @@ -15,3 +15,16 @@ DO $$ warn $_SHARED{init} $$ language plperlu; CREATE OR REPLACE FUNCTION perl_unicode_regex(text) RETURNS INTEGER AS $$ return ($_[0] =~ /\x{263A}|happy/i) ? 1 : 0; # unicode smiley $$ LANGUAGE plperlu; + +-- croak from overload stringify after the sub returns. +CREATE TABLE overload_trig_test (); + +CREATE FUNCTION overload_trig_stringify_die() RETURNS trigger AS $$ + use overload '""' => sub { die 'overload stringify died' }; + bless {}; +$$ LANGUAGE plperlu; + +CREATE TRIGGER overload_trig_stringify_die_trig BEFORE INSERT ON overload_trig_test +EXECUTE PROCEDURE overload_trig_stringify_die(); + +INSERT INTO overload_trig_test DEFAULT VALUES; -- 2.53.0
