summaryrefslogtreecommitdiff
path: root/src/pl/plperl/plperl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/plperl/plperl.c')
-rw-r--r--src/pl/plperl/plperl.c783
1 files changed, 0 insertions, 783 deletions
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
deleted file mode 100644
index 7533e578436..00000000000
--- a/src/pl/plperl/plperl.c
+++ /dev/null
@@ -1,783 +0,0 @@
-/**********************************************************************
- * plperl.c - perl as a procedural language for PostgreSQL
- *
- * IDENTIFICATION
- *
- * This software is copyrighted by Mark Hollomon
- * but is shameless cribbed from pltcl.c by Jan Weick.
- *
- * The author hereby grants permission to use, copy, modify,
- * distribute, and license this software and its documentation
- * for any purpose, provided that existing copyright notices are
- * retained in all copies and that this notice is included
- * verbatim in any distributions. No written agreement, license,
- * or royalty fee is required for any of the authorized uses.
- * Modifications to this software may be copyrighted by their
- * author and need not follow the licensing terms described
- * here, provided that the new terms are clearly indicated on
- * the first page of each file where they apply.
- *
- * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
- * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
- * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
- * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
- * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
- * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
- * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
- * ENHANCEMENTS, OR MODIFICATIONS.
- *
- * IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plperl/plperl.c,v 1.31 2002/06/15 19:54:24 momjian Exp $
- *
- **********************************************************************/
-
-#include "postgres.h"
-
-/* system stuff */
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <setjmp.h>
-
-/* postgreSQL stuff */
-#include "executor/spi.h"
-#include "commands/trigger.h"
-#include "utils/elog.h"
-#include "fmgr.h"
-#include "access/heapam.h"
-
-#include "tcop/tcopprot.h"
-#include "utils/syscache.h"
-#include "catalog/pg_language.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
-
-/* perl stuff */
-#include "EXTERN.h"
-#include "perl.h"
-#include "XSUB.h"
-#include "ppport.h"
-
-/* just in case these symbols aren't provided */
-#ifndef pTHX_
-#define pTHX_
-#define pTHX void
-#endif
-
-
-/**********************************************************************
- * The information we cache about loaded procedures
- **********************************************************************/
-typedef struct plperl_proc_desc
-{
- char *proname;
- TransactionId fn_xmin;
- CommandId fn_cmin;
- bool lanpltrusted;
- FmgrInfo result_in_func;
- Oid result_in_elem;
- int nargs;
- FmgrInfo arg_out_func[FUNC_MAX_ARGS];
- Oid arg_out_elem[FUNC_MAX_ARGS];
- int arg_is_rel[FUNC_MAX_ARGS];
- SV *reference;
-} plperl_proc_desc;
-
-
-/**********************************************************************
- * Global data
- **********************************************************************/
-static int plperl_firstcall = 1;
-static int plperl_call_level = 0;
-static int plperl_restart_in_progress = 0;
-static PerlInterpreter *plperl_interp = NULL;
-static HV *plperl_proc_hash = NULL;
-
-/**********************************************************************
- * Forward declarations
- **********************************************************************/
-static void plperl_init_all(void);
-static void plperl_init_interp(void);
-
-Datum plperl_call_handler(PG_FUNCTION_ARGS);
-
-static Datum plperl_func_handler(PG_FUNCTION_ARGS);
-
-static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
-
-static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
-static void plperl_init_shared_libs(pTHX);
-
-
-/*
- * This routine is a crock, and so is everyplace that calls it. The problem
- * is that the cached form of plperl functions/queries is allocated permanently
- * (mostly via malloc()) and never released until backend exit. Subsidiary
- * data structures such as fmgr info records therefore must live forever
- * as well. A better implementation would store all this stuff in a per-
- * function memory context that could be reclaimed at need. In the meantime,
- * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever
- * it might allocate, and whatever the eventual function might allocate using
- * fn_mcxt, will live forever too.
- */
-static void
-perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
-{
- fmgr_info_cxt(functionId, finfo, TopMemoryContext);
-}
-
-/**********************************************************************
- * plperl_init_all() - Initialize all
- **********************************************************************/
-static void
-plperl_init_all(void)
-{
-
- /************************************************************
- * Do initialization only once
- ************************************************************/
- if (!plperl_firstcall)
- return;
-
-
- /************************************************************
- * Destroy the existing Perl interpreter
- ************************************************************/
- if (plperl_interp != NULL)
- {
- perl_destruct(plperl_interp);
- perl_free(plperl_interp);
- plperl_interp = NULL;
- }
-
- /************************************************************
- * Free the proc hash table
- ************************************************************/
- if (plperl_proc_hash != NULL)
- {
- hv_undef(plperl_proc_hash);
- SvREFCNT_dec((SV *) plperl_proc_hash);
- plperl_proc_hash = NULL;
- }
-
- /************************************************************
- * Now recreate a new Perl interpreter
- ************************************************************/
- plperl_init_interp();
-
- plperl_firstcall = 0;
-}
-
-
-/**********************************************************************
- * plperl_init_interp() - Create the Perl interpreter
- **********************************************************************/
-static void
-plperl_init_interp(void)
-{
-
- char *embedding[3] = {
- "", "-e",
-
- /*
- * no commas between the next 5 please. They are supposed to be
- * one string
- */
- "require Safe; SPI::bootstrap();"
- "sub ::mksafefunc { my $x = new Safe; $x->permit_only(':default');$x->permit(':base_math');"
- "$x->share(qw[&elog &DEBUG &LOG &INFO &NOTICE &WARNING &ERROR]);"
- " return $x->reval(qq[sub { $_[0] }]); }"
- "sub ::mkunsafefunc {return eval(qq[ sub { $_[0] } ]); }"
- };
-
- plperl_interp = perl_alloc();
- if (!plperl_interp)
- elog(ERROR, "plperl_init_interp(): could not allocate perl interpreter");
-
- perl_construct(plperl_interp);
- perl_parse(plperl_interp, plperl_init_shared_libs, 3, embedding, NULL);
- perl_run(plperl_interp);
-
-
-
- /************************************************************
- * Initialize the proc and query hash tables
- ************************************************************/
- plperl_proc_hash = newHV();
-
-}
-
-
-
-/**********************************************************************
- * plperl_call_handler - This is the only visible function
- * of the PL interpreter. The PostgreSQL
- * function manager and trigger manager
- * call this function for execution of
- * perl procedures.
- **********************************************************************/
-PG_FUNCTION_INFO_V1(plperl_call_handler);
-
-/* keep non-static */
-Datum
-plperl_call_handler(PG_FUNCTION_ARGS)
-{
- Datum retval;
-
- /************************************************************
- * Initialize interpreters on first call
- ************************************************************/
- if (plperl_firstcall)
- plperl_init_all();
-
- /************************************************************
- * Connect to SPI manager
- ************************************************************/
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(ERROR, "plperl: cannot connect to SPI manager");
- /************************************************************
- * Keep track about the nesting of Perl-SPI-Perl-... calls
- ************************************************************/
- plperl_call_level++;
-
- /************************************************************
- * Determine if called as function or trigger and
- * call appropriate subhandler
- ************************************************************/
- if (CALLED_AS_TRIGGER(fcinfo))
- {
- elog(ERROR, "plperl: can't use perl in triggers yet.");
-
- /*
- * retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
- */
- /* make the compiler happy */
- retval = (Datum) 0;
- }
- else
- retval = plperl_func_handler(fcinfo);
-
- plperl_call_level--;
-
- return retval;
-}
-
-
-/**********************************************************************
- * plperl_create_sub() - calls the perl interpreter to
- * create the anonymous subroutine whose text is in the SV.
- * Returns the SV containing the RV to the closure.
- **********************************************************************/
-static
-SV *
-plperl_create_sub(char *s, bool trusted)
-{
- dSP;
-
- SV *subref = NULL;
- int count;
-
- ENTER;
- SAVETMPS;
- PUSHMARK(SP);
- XPUSHs(sv_2mortal(newSVpv(s, 0)));
- PUTBACK;
- count = perl_call_pv((trusted ? "mksafefunc" : "mkunsafefunc"),
- G_SCALAR | G_EVAL | G_KEEPERR);
- SPAGAIN;
-
- if (SvTRUE(ERRSV))
- {
- POPs;
- PUTBACK;
- FREETMPS;
- LEAVE;
- elog(ERROR, "creation of function failed: %s", SvPV(ERRSV, PL_na));
- }
-
- if (count != 1)
- elog(ERROR, "creation of function failed - no return from mksafefunc");
-
- /*
- * need to make a deep copy of the return. it comes off the stack as a
- * temporary.
- */
- subref = newSVsv(POPs);
-
- if (!SvROK(subref))
- {
- PUTBACK;
- FREETMPS;
- LEAVE;
-
- /*
- * subref is our responsibility because it is not mortal
- */
- SvREFCNT_dec(subref);
- elog(ERROR, "plperl_create_sub: didn't get a code ref");
- }
-
- PUTBACK;
- FREETMPS;
- LEAVE;
- return subref;
-}
-
-/**********************************************************************
- * plperl_init_shared_libs() -
- *
- * We cannot use the DynaLoader directly to get at the Opcode
- * module (used by Safe.pm). So, we link Opcode into ourselves
- * and do the initialization behind perl's back.
- *
- **********************************************************************/
-
-EXTERN_C void boot_DynaLoader(pTHX_ CV* cv);
-EXTERN_C void boot_SPI(pTHX_ CV* cv);
-
-static void
-plperl_init_shared_libs(pTHX)
-{
- char *file = __FILE__;
-
- newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
- newXS("SPI::bootstrap", boot_SPI, file);
-}
-
-/**********************************************************************
- * plperl_call_perl_func() - calls a perl function through the RV
- * stored in the prodesc structure. massages the input parms properly
- **********************************************************************/
-static
-SV *
-plperl_call_perl_func(plperl_proc_desc * desc, FunctionCallInfo fcinfo)
-{
- dSP;
-
- SV *retval;
- int i;
- int count;
-
-
- ENTER;
- SAVETMPS;
-
- PUSHMARK(sp);
- for (i = 0; i < desc->nargs; i++)
- {
- if (desc->arg_is_rel[i])
- {
- TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i];
- SV *hashref;
-
- Assert(slot != NULL && !fcinfo->argnull[i]);
-
- /*
- * plperl_build_tuple_argument better return a mortal SV.
- */
- hashref = plperl_build_tuple_argument(slot->val,
- slot->ttc_tupleDescriptor);
- XPUSHs(hashref);
- }
- else
- {
- if (fcinfo->argnull[i])
- XPUSHs(&PL_sv_undef);
- else
- {
- char *tmp;
-
- tmp = DatumGetCString(FunctionCall3(&(desc->arg_out_func[i]),
- fcinfo->arg[i],
- ObjectIdGetDatum(desc->arg_out_elem[i]),
- Int32GetDatum(-1)));
- XPUSHs(sv_2mortal(newSVpv(tmp, 0)));
- pfree(tmp);
- }
- }
- }
- PUTBACK;
- count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL | G_KEEPERR);
-
- SPAGAIN;
-
- if (count != 1)
- {
- PUTBACK;
- FREETMPS;
- LEAVE;
- elog(ERROR, "plperl: didn't get a return item from function");
- }
-
- if (SvTRUE(ERRSV))
- {
- POPs;
- PUTBACK;
- FREETMPS;
- LEAVE;
- elog(ERROR, "plperl: error from function: %s", SvPV(ERRSV, PL_na));
- }
-
- retval = newSVsv(POPs);
-
-
- PUTBACK;
- FREETMPS;
- LEAVE;
-
- return retval;
-
-
-}
-
-/**********************************************************************
- * plperl_func_handler() - Handler for regular function calls
- **********************************************************************/
-static Datum
-plperl_func_handler(PG_FUNCTION_ARGS)
-{
- plperl_proc_desc *prodesc;
- SV *perlret;
- Datum retval;
- sigjmp_buf save_restart;
-
- /* Find or compile the function */
- prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
-
- /* Set up error handling */
- memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
-
- if (sigsetjmp(Warn_restart, 1) != 0)
- {
- memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
- plperl_restart_in_progress = 1;
- if (--plperl_call_level == 0)
- plperl_restart_in_progress = 0;
- siglongjmp(Warn_restart, 1);
- }
-
- /************************************************************
- * Call the Perl function
- ************************************************************/
- perlret = plperl_call_perl_func(prodesc, fcinfo);
-
- /************************************************************
- * Disconnect from SPI manager and then create the return
- * values datum (if the input function does a palloc for it
- * this must not be allocated in the SPI memory context
- * because SPI_finish would free it).
- ************************************************************/
- if (SPI_finish() != SPI_OK_FINISH)
- elog(ERROR, "plperl: SPI_finish() failed");
-
- if (!(perlret && SvOK(perlret)))
- {
- /* return NULL if Perl code returned undef */
- retval = (Datum) 0;
- fcinfo->isnull = true;
- }
- else
- {
- retval = FunctionCall3(&prodesc->result_in_func,
- PointerGetDatum(SvPV(perlret, PL_na)),
- ObjectIdGetDatum(prodesc->result_in_elem),
- Int32GetDatum(-1));
- }
-
- SvREFCNT_dec(perlret);
-
- memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
- if (plperl_restart_in_progress)
- {
- if (--plperl_call_level == 0)
- plperl_restart_in_progress = 0;
- siglongjmp(Warn_restart, 1);
- }
-
- return retval;
-}
-
-
-/**********************************************************************
- * compile_plperl_function - compile (or hopefully just look up) function
- **********************************************************************/
-static plperl_proc_desc *
-compile_plperl_function(Oid fn_oid, bool is_trigger)
-{
- HeapTuple procTup;
- Form_pg_proc procStruct;
- char internal_proname[64];
- int proname_len;
- plperl_proc_desc *prodesc = NULL;
- int i;
-
- /* We'll need the pg_proc tuple in any case... */
- procTup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(fn_oid),
- 0, 0, 0);
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "plperl: cache lookup for proc %u failed", fn_oid);
- procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-
- /************************************************************
- * Build our internal proc name from the functions Oid
- ************************************************************/
- if (!is_trigger)
- sprintf(internal_proname, "__PLPerl_proc_%u", fn_oid);
- else
- sprintf(internal_proname, "__PLPerl_proc_%u_trigger", fn_oid);
- proname_len = strlen(internal_proname);
-
- /************************************************************
- * Lookup the internal proc name in the hashtable
- ************************************************************/
- if (hv_exists(plperl_proc_hash, internal_proname, proname_len))
- {
- bool uptodate;
-
- prodesc = (plperl_proc_desc *) SvIV(*hv_fetch(plperl_proc_hash,
- internal_proname, proname_len, 0));
-
- /************************************************************
- * If it's present, must check whether it's still up to date.
- * This is needed because CREATE OR REPLACE FUNCTION can modify the
- * function's pg_proc entry without changing its OID.
- ************************************************************/
- uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
-
- if (!uptodate)
- {
- /* need we delete old entry? */
- prodesc = NULL;
- }
- }
-
- /************************************************************
- * If we haven't found it in the hashtable, we analyze
- * the functions arguments and returntype and store
- * the in-/out-functions in the prodesc block and create
- * a new hashtable entry for it.
- *
- * Then we load the procedure into the Perl interpreter.
- ************************************************************/
- if (prodesc == NULL)
- {
- HeapTuple langTup;
- HeapTuple typeTup;
- Form_pg_language langStruct;
- Form_pg_type typeStruct;
- char *proc_source;
-
- /************************************************************
- * Allocate a new procedure description block
- ************************************************************/
- prodesc = (plperl_proc_desc *) malloc(sizeof(plperl_proc_desc));
- if (prodesc == NULL)
- elog(ERROR, "plperl: out of memory");
- MemSet(prodesc, 0, sizeof(plperl_proc_desc));
- prodesc->proname = strdup(internal_proname);
- prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
-
- /************************************************************
- * Lookup the pg_language tuple by Oid
- ************************************************************/
- langTup = SearchSysCache(LANGOID,
- ObjectIdGetDatum(procStruct->prolang),
- 0, 0, 0);
- if (!HeapTupleIsValid(langTup))
- {
- free(prodesc->proname);
- free(prodesc);
- elog(ERROR, "plperl: cache lookup for language %u failed",
- procStruct->prolang);
- }
- langStruct = (Form_pg_language) GETSTRUCT(langTup);
- prodesc->lanpltrusted = langStruct->lanpltrusted;
- ReleaseSysCache(langTup);
-
- /************************************************************
- * Get the required information for input conversion of the
- * return value.
- ************************************************************/
- if (!is_trigger)
- {
- typeTup = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(procStruct->prorettype),
- 0, 0, 0);
- if (!HeapTupleIsValid(typeTup))
- {
- free(prodesc->proname);
- free(prodesc);
- if (!OidIsValid(procStruct->prorettype))
- elog(ERROR, "plperl functions cannot return type \"opaque\""
- "\n\texcept when used as triggers");
- else
- elog(ERROR, "plperl: cache lookup for return type %u failed",
- procStruct->prorettype);
- }
- typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
-
- if (typeStruct->typrelid != InvalidOid)
- {
- free(prodesc->proname);
- free(prodesc);
- elog(ERROR, "plperl: return types of tuples not supported yet");
- }
-
- perm_fmgr_info(typeStruct->typinput, &(prodesc->result_in_func));
- prodesc->result_in_elem = typeStruct->typelem;
-
- ReleaseSysCache(typeTup);
- }
-
- /************************************************************
- * Get the required information for output conversion
- * of all procedure arguments
- ************************************************************/
- if (!is_trigger)
- {
- prodesc->nargs = procStruct->pronargs;
- for (i = 0; i < prodesc->nargs; i++)
- {
- typeTup = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(procStruct->proargtypes[i]),
- 0, 0, 0);
- if (!HeapTupleIsValid(typeTup))
- {
- free(prodesc->proname);
- free(prodesc);
- if (!OidIsValid(procStruct->proargtypes[i]))
- elog(ERROR, "plperl functions cannot take type \"opaque\"");
- else
- elog(ERROR, "plperl: cache lookup for argument type %u failed",
- procStruct->proargtypes[i]);
- }
- typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
-
- if (typeStruct->typrelid != InvalidOid)
- prodesc->arg_is_rel[i] = 1;
- else
- prodesc->arg_is_rel[i] = 0;
-
- perm_fmgr_info(typeStruct->typoutput, &(prodesc->arg_out_func[i]));
- prodesc->arg_out_elem[i] = typeStruct->typelem;
- ReleaseSysCache(typeTup);
- }
- }
-
- /************************************************************
- * create the text of the anonymous subroutine.
- * we do not use a named subroutine so that we can call directly
- * through the reference.
- *
- ************************************************************/
- proc_source = DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(&procStruct->prosrc)));
-
- /************************************************************
- * Create the procedure in the interpreter
- ************************************************************/
- prodesc->reference = plperl_create_sub(proc_source, prodesc->lanpltrusted);
- pfree(proc_source);
- if (!prodesc->reference)
- {
- free(prodesc->proname);
- free(prodesc);
- elog(ERROR, "plperl: cannot create internal procedure %s",
- internal_proname);
- }
-
- /************************************************************
- * Add the proc description block to the hashtable
- ************************************************************/
- hv_store(plperl_proc_hash, internal_proname, proname_len,
- newSViv((IV) prodesc), 0);
- }
-
- ReleaseSysCache(procTup);
-
- return prodesc;
-}
-
-
-/**********************************************************************
- * plperl_build_tuple_argument() - Build a string for a ref to a hash
- * from all attributes of a given tuple
- **********************************************************************/
-static SV *
-plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
-{
- int i;
- SV *output;
- Datum attr;
- bool isnull;
- char *attname;
- char *outputstr;
- HeapTuple typeTup;
- Oid typoutput;
- Oid typelem;
-
- output = sv_2mortal(newSVpv("{", 0));
-
- for (i = 0; i < tupdesc->natts; i++)
- {
- /************************************************************
- * Get the attribute name
- ************************************************************/
- attname = tupdesc->attrs[i]->attname.data;
-
- /************************************************************
- * Get the attributes value
- ************************************************************/
- attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
-
- /************************************************************
- * If it is null it will be set to undef in the hash.
- ************************************************************/
- if (isnull)
- {
- sv_catpvf(output, "'%s' => undef,", attname);
- continue;
- }
-
- /************************************************************
- * 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, "plperl: Cache lookup for attribute '%s' type %u failed",
- attname, tupdesc->attrs[i]->atttypid);
-
- typoutput = ((Form_pg_type) GETSTRUCT(typeTup))->typoutput;
- typelem = ((Form_pg_type) GETSTRUCT(typeTup))->typelem;
- ReleaseSysCache(typeTup);
-
- /************************************************************
- * Append the attribute name and the value to the list.
- ************************************************************/
- outputstr = DatumGetCString(OidFunctionCall3(typoutput,
- attr,
- ObjectIdGetDatum(typelem),
- Int32GetDatum(tupdesc->attrs[i]->atttypmod)));
- sv_catpvf(output, "'%s' => '%s',", attname, outputstr);
- pfree(outputstr);
- }
-
- sv_catpv(output, "}");
- output = perl_eval_pv(SvPV(output, PL_na), TRUE);
- return output;
-}