Files
postgres/src/pl/plpython/plpython.c
T
Tom Lane 375369acd1 Replace TupleTableSlot convention for whole-row variables and function
results with tuples as ordinary varlena Datums.  This commit does not
in itself do much for us, except eliminate the horrid memory leak
associated with evaluation of whole-row variables.  However, it lays the
groundwork for allowing composite types as table columns, and perhaps
some other useful features as well.  Per my proposal of a few days ago.
2004-04-01 21:28:47 +00:00

2819 lines
61 KiB
C

/**********************************************************************
* plpython.c - python as a procedural language for PostgreSQL
*
* This software is copyright by Andrew Bosma
* but is really shameless cribbed from pltcl.c by Jan Weick, and
* plperl.c by Mark Hollomon.
*
* 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
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.46 2004/04/01 21:28:46 tgl Exp $
*
*********************************************************************
*/
#include "postgres.h"
/* system stuff */
#include <unistd.h>
#include <fcntl.h>
#include <setjmp.h>
/* postgreSQL stuff */
#include "access/heapam.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "tcop/tcopprot.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include <Python.h>
#include <compile.h>
#include <eval.h>
#include "plpython.h"
/* convert Postgresql Datum or tuple into a PyObject.
* input to Python. Tuples are converted to dictionary
* objects.
*/
typedef PyObject *(*PLyDatumToObFunc) (const char *);
typedef struct PLyDatumToOb
{
PLyDatumToObFunc func;
FmgrInfo typfunc;
Oid typelem;
bool typbyval;
} PLyDatumToOb;
typedef struct PLyTupleToOb
{
PLyDatumToOb *atts;
int natts;
} PLyTupleToOb;
typedef union PLyTypeInput
{
PLyDatumToOb d;
PLyTupleToOb r;
} PLyTypeInput;
/* convert PyObject to a Postgresql Datum or tuple.
* output from Python
*/
typedef struct PLyObToDatum
{
FmgrInfo typfunc;
Oid typelem;
bool typbyval;
} PLyObToDatum;
typedef struct PLyObToTuple
{
PLyObToDatum *atts;
int natts;
} PLyObToTuple;
typedef union PLyTypeOutput
{
PLyObToDatum d;
PLyObToTuple r;
} PLyTypeOutput;
/* all we need to move Postgresql data to Python objects,
* and vis versa
*/
typedef struct PLyTypeInfo
{
PLyTypeInput in;
PLyTypeOutput out;
int is_rowtype;
/*
* is_rowtype can be:
* -1 not known yet (initial state)
* 0 scalar datatype
* 1 rowtype
* 2 rowtype, but I/O functions not set up yet
*/
} PLyTypeInfo;
/* cached procedure data
*/
typedef struct PLyProcedure
{
char *proname; /* SQL name of procedure */
char *pyname; /* Python name of procedure */
TransactionId fn_xmin;
CommandId fn_cmin;
PLyTypeInfo result; /* also used to store info for trigger
* tuple type */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global score */
PyObject *me; /* PyCObject containing pointer to this
* PLyProcedure */
} PLyProcedure;
/* Python objects.
*/
typedef struct PLyPlanObject
{
PyObject_HEAD
void *plan; /* return of an SPI_saveplan */
int nargs;
Oid *types;
Datum *values;
PLyTypeInfo *args;
} PLyPlanObject;
typedef struct PLyResultObject
{
PyObject_HEAD
/* HeapTuple *tuples; */
PyObject *nrows; /* number of rows returned by query */
PyObject *rows; /* data rows, or None if no data returned */
PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
} PLyResultObject;
/* function declarations
*/
/* Two exported functions: first is the magic telling Postgresql
* what function call interface it implements. Second allows
* preinitialization of the interpreter during postmaster startup.
*/
Datum plpython_call_handler(PG_FUNCTION_ARGS);
void plpython_init(void);
PG_FUNCTION_INFO_V1(plpython_call_handler);
/* most of the remaining of the declarations, all static
*/
/* these should only be called once at the first call
* of plpython_call_handler. initialize the python interpreter
* and global data.
*/
static void PLy_init_all(void);
static void PLy_init_interp(void);
static void PLy_init_plpy(void);
/* error handler. collects the current Python exception, if any,
* and appends it to the error and sends it to elog
*/
static void PLy_elog(int, const char *,...);
/* call PyErr_SetString with a vprint interface
*/
static void
PLy_exception_set(PyObject *, const char *,...)
__attribute__((format(printf, 2, 3)));
/* Get the innermost python procedure called from the backend.
*/
static char *PLy_procedure_name(PLyProcedure *);
/* some utility functions
*/
static void *PLy_malloc(size_t);
static void *PLy_realloc(void *, size_t);
static void PLy_free(void *);
/* sub handlers for functions and triggers
*/
static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *);
static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *);
static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *);
static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *,
HeapTuple *);
static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
TriggerData *, HeapTuple);
static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *);
static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo,
Oid tgreloid);
static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo,
Oid tgreloid,
HeapTuple procTup, char *key);
static void PLy_procedure_compile(PLyProcedure *, const char *);
static char *PLy_procedure_munge_source(const char *, const char *);
static void PLy_procedure_delete(PLyProcedure *);
static void PLy_typeinfo_init(PLyTypeInfo *);
static void PLy_typeinfo_dealloc(PLyTypeInfo *);
static void PLy_output_datum_func(PLyTypeInfo *, Form_pg_type);
static void PLy_output_datum_func2(PLyObToDatum *, Form_pg_type);
static void PLy_input_datum_func(PLyTypeInfo *, Oid, Form_pg_type);
static void PLy_input_datum_func2(PLyDatumToOb *, Oid, Form_pg_type);
static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
/* conversion functions
*/
static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
static PyObject *PLyBool_FromString(const char *);
static PyObject *PLyFloat_FromString(const char *);
static PyObject *PLyInt_FromString(const char *);
static PyObject *PLyLong_FromString(const char *);
static PyObject *PLyString_FromString(const char *);
/* global data
*/
static int PLy_first_call = 1;
static volatile int PLy_call_level = 0;
/*
* Last function called by postgres backend
*/
static PLyProcedure *PLy_last_procedure = NULL;
/* this gets modified in plpython_call_handler and PLy_elog.
* test it any old where, but do NOT modify it anywhere except
* those two functions
*/
static volatile int PLy_restart_in_progress = 0;
static PyObject *PLy_interp_globals = NULL;
static PyObject *PLy_interp_safe_globals = NULL;
static PyObject *PLy_procedure_cache = NULL;
/* Python exceptions
*/
static PyObject *PLy_exc_error = NULL;
static PyObject *PLy_exc_fatal = NULL;
static PyObject *PLy_exc_spi_error = NULL;
/* some globals for the python module
*/
static char PLy_plan_doc[] = {
"Store a PostgreSQL plan"
};
static char PLy_result_doc[] = {
"Results of a PostgreSQL query"
};
#if DEBUG_EXC
volatile int exc_save_calls = 0;
volatile int exc_restore_calls = 0;
volatile int func_enter_calls = 0;
volatile int func_leave_calls = 0;
#endif
/*
* the function definitions
*/
/*
* This routine is a crock, and so is everyplace that calls it. The problem
* is that the cached form of plpython 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);
}
Datum
plpython_call_handler(PG_FUNCTION_ARGS)
{
DECLARE_EXC();
Datum retval;
PLyProcedure *volatile proc = NULL;
enter();
PLy_init_all();
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
CALL_LEVEL_INC();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
CALL_LEVEL_DEC();
if (PLy_call_level == 0)
{
PLy_restart_in_progress = 0;
PyErr_Clear();
}
else
PLy_restart_in_progress += 1;
if (proc)
{
/* note: Py_DECREF needs braces around it, as of 2003/08 */
Py_DECREF(proc->me);
}
RERAISE_EXC();
}
/*
* elog(DEBUG3, "PLy_restart_in_progress is %d",
* PLy_restart_in_progress);
*/
if (CALLED_AS_TRIGGER(fcinfo))
{
TriggerData *tdata = (TriggerData *) fcinfo->context;
HeapTuple trv;
proc = PLy_procedure_get(fcinfo,
RelationGetRelid(tdata->tg_relation));
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
proc = PLy_procedure_get(fcinfo, InvalidOid);
retval = PLy_function_handler(fcinfo, proc);
}
CALL_LEVEL_DEC();
RESTORE_EXC();
Py_DECREF(proc->me);
refc(proc->me);
return retval;
}
/* trigger and function sub handlers
*
* the python function is expected to return Py_None if the tuple is
* acceptable and unmodified. Otherwise it should return a PyString
* object who's value is SKIP, or MODIFY. SKIP means don't perform
* this action. MODIFY means the tuple has been modified, so update
* tuple and perform action. SKIP and MODIFY assume the trigger fires
* BEFORE the event and is ROW level. postgres expects the function
* to take no arguments and return an argument of type trigger.
*/
HeapTuple
PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
{
DECLARE_EXC();
HeapTuple rv = NULL;
PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plargs);
Py_XDECREF(plrv);
RERAISE_EXC();
}
plargs = PLy_trigger_build_args(fcinfo, proc, &rv);
plrv = PLy_procedure_call(proc, "TD", plargs);
/*
* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
if (plrv == NULL)
elog(FATAL, "PLy_procedure_call returned NULL");
if (PLy_restart_in_progress)
elog(FATAL, "restart in progress not expected");
/*
* return of None means we're happy with the tuple
*/
if (plrv != Py_None)
{
char *srv;
if (!PyString_Check(plrv))
elog(ERROR, "expected trigger to return None or a String");
srv = PyString_AsString(plrv);
if (strcasecmp(srv, "SKIP") == 0)
rv = NULL;
else if (strcasecmp(srv, "MODIFY") == 0)
{
TriggerData *tdata = (TriggerData *) fcinfo->context;
if ((TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) ||
(TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)))
rv = PLy_modify_tuple(proc, plargs, tdata, rv);
else
elog(WARNING, "ignoring modified tuple in DELETE trigger");
}
else if (strcasecmp(srv, "OK"))
{
/*
* hmmm, perhaps they only read the pltcl page, not a
* surprising thing since i've written no documentation, so
* accept a belated OK
*/
elog(ERROR, "expected return to be \"SKIP\" or \"MODIFY\"");
}
}
Py_DECREF(plargs);
Py_DECREF(plrv);
RESTORE_EXC();
return rv;
}
HeapTuple
PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata,
HeapTuple otup)
{
DECLARE_EXC();
PyObject *volatile plntup;
PyObject *volatile plkeys;
PyObject *volatile platt;
PyObject *volatile plval;
PyObject *volatile plstr;
HeapTuple rtup;
int natts,
i,
attn,
atti;
int *volatile modattrs;
Datum *volatile modvalues;
char *volatile modnulls;
TupleDesc tupdesc;
plntup = plkeys = platt = plval = plstr = NULL;
modattrs = NULL;
modvalues = NULL;
modnulls = NULL;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plntup);
Py_XDECREF(plkeys);
Py_XDECREF(platt);
Py_XDECREF(plval);
Py_XDECREF(plstr);
if (modnulls)
pfree(modnulls);
if (modvalues)
pfree(modvalues);
if (modattrs)
pfree(modattrs);
RERAISE_EXC();
}
if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL)
elog(ERROR, "TD[\"new\"] deleted, unable to modify tuple");
if (!PyDict_Check(plntup))
elog(ERROR, "TD[\"new\"] is not a dictionary object");
Py_INCREF(plntup);
plkeys = PyDict_Keys(plntup);
natts = PyList_Size(plkeys);
/* +1 to avoid palloc(0) on empty tuple */
modattrs = palloc(natts * sizeof(int) + 1);
modvalues = palloc(natts * sizeof(Datum) + 1);
modnulls = palloc(natts + 1);
tupdesc = tdata->tg_relation->rd_att;
for (i = 0; i < natts; i++)
{
char *src;
platt = PyList_GetItem(plkeys, i);
if (!PyString_Check(platt))
elog(ERROR, "attribute name is not a string");
attn = SPI_fnumber(tupdesc, PyString_AsString(platt));
if (attn == SPI_ERROR_NOATTRIBUTE)
elog(ERROR, "invalid attribute \"%s\" in tuple",
PyString_AsString(platt));
atti = attn - 1;
plval = PyDict_GetItem(plntup, platt);
if (plval == NULL)
elog(FATAL, "python interpreter is probably corrupted");
Py_INCREF(plval);
modattrs[i] = attn;
if (plval != Py_None && !tupdesc->attrs[atti]->attisdropped)
{
plstr = PyObject_Str(plval);
src = PyString_AsString(plstr);
modvalues[i] = FunctionCall3(&proc->result.out.r.atts[atti].typfunc,
CStringGetDatum(src),
ObjectIdGetDatum(proc->result.out.r.atts[atti].typelem),
Int32GetDatum(tupdesc->attrs[atti]->atttypmod));
modnulls[i] = ' ';
Py_DECREF(plstr);
plstr = NULL;
}
else
{
modvalues[i] = (Datum) 0;
modnulls[i] = 'n';
}
Py_DECREF(plval);
plval = NULL;
}
rtup = SPI_modifytuple(tdata->tg_relation, otup, natts,
modattrs, modvalues, modnulls);
/*
* FIXME -- these leak if not explicitly pfree'd by other elog calls,
* no? (No, I think, but might as well leave the pfrees here...)
*/
pfree(modattrs);
pfree(modvalues);
pfree(modnulls);
if (rtup == NULL)
elog(ERROR, "SPI_modifytuple failed -- error %d", SPI_result);
Py_DECREF(plntup);
Py_DECREF(plkeys);
RESTORE_EXC();
return rtup;
}
PyObject *
PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple *rv)
{
DECLARE_EXC();
TriggerData *tdata;
PyObject *pltname,
*pltevent,
*pltwhen,
*pltlevel,
*pltrelid;
PyObject *pltargs,
*pytnew,
*pytold;
PyObject *volatile pltdata = NULL;
char *stroid;
enter();
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(pltdata);
RERAISE_EXC();
}
tdata = (TriggerData *) fcinfo->context;
pltdata = PyDict_New();
if (!pltdata)
PLy_elog(ERROR, "could not build arguments for trigger procedure");
pltname = PyString_FromString(tdata->tg_trigger->tgname);
PyDict_SetItemString(pltdata, "name", pltname);
Py_DECREF(pltname);
stroid = DatumGetCString(DirectFunctionCall1(oidout,
ObjectIdGetDatum(tdata->tg_relation->rd_id)));
pltrelid = PyString_FromString(stroid);
PyDict_SetItemString(pltdata, "relid", pltrelid);
Py_DECREF(pltrelid);
pfree(stroid);
if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
pltwhen = PyString_FromString("BEFORE");
else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
pltwhen = PyString_FromString("AFTER");
else
{
elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event);
pltwhen = NULL; /* keep compiler quiet */
}
PyDict_SetItemString(pltdata, "when", pltwhen);
Py_DECREF(pltwhen);
if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
{
pltlevel = PyString_FromString("ROW");
PyDict_SetItemString(pltdata, "level", pltlevel);
Py_DECREF(pltlevel);
if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
{
pltevent = PyString_FromString("INSERT");
PyDict_SetItemString(pltdata, "old", Py_None);
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
*rv = tdata->tg_trigtuple;
}
else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
{
pltevent = PyString_FromString("DELETE");
PyDict_SetItemString(pltdata, "new", Py_None);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_trigtuple;
}
else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
{
pltevent = PyString_FromString("UPDATE");
pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "new", pytnew);
Py_DECREF(pytnew);
pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
tdata->tg_relation->rd_att);
PyDict_SetItemString(pltdata, "old", pytold);
Py_DECREF(pytold);
*rv = tdata->tg_newtuple;
}
else
{
elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
pltevent = NULL; /* keep compiler quiet */
}
PyDict_SetItemString(pltdata, "event", pltevent);
Py_DECREF(pltevent);
}
else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
{
pltlevel = PyString_FromString("STATEMENT");
PyDict_SetItemString(pltdata, "level", pltlevel);
Py_DECREF(pltlevel);
PyDict_SetItemString(pltdata, "old", Py_None);
PyDict_SetItemString(pltdata, "new", Py_None);
*rv = NULL;
if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
pltevent = PyString_FromString("INSERT");
else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
pltevent = PyString_FromString("DELETE");
else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
pltevent = PyString_FromString("UPDATE");
else
{
elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
pltevent = NULL; /* keep compiler quiet */
}
PyDict_SetItemString(pltdata, "event", pltevent);
Py_DECREF(pltevent);
}
else
elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event);
if (tdata->tg_trigger->tgnargs)
{
/*
* all strings...
*/
int i;
PyObject *pltarg;
pltargs = PyList_New(tdata->tg_trigger->tgnargs);
for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
{
pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]);
/*
* stolen, don't Py_DECREF
*/
PyList_SetItem(pltargs, i, pltarg);
}
}
else
{
Py_INCREF(Py_None);
pltargs = Py_None;
}
PyDict_SetItemString(pltdata, "args", pltargs);
Py_DECREF(pltargs);
RESTORE_EXC();
return pltdata;
}
/* function handler and friends
*/
Datum
PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
{
DECLARE_EXC();
Datum rv;
PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL;
PyObject *volatile plrv_so = NULL;
char *plrv_sc;
enter();
/*
* setup to catch elog in while building function arguments, and
* DECREF the plargs if the function call fails
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(plargs);
Py_XDECREF(plrv);
Py_XDECREF(plrv_so);
RERAISE_EXC();
}
plargs = PLy_function_build_args(fcinfo, proc);
plrv = PLy_procedure_call(proc, "args", plargs);
/*
* 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, "SPI_finish failed");
if (plrv == NULL)
{
elog(FATAL, "PLy_procedure_call returned NULL");
#ifdef NOT_USED
if (!PLy_restart_in_progress)
PLy_elog(ERROR, "function \"%s\" failed", proc->proname);
/*
* FIXME is this dead code? i'm pretty sure it is for unnested
* calls, but not for nested calls
*/
RAISE_EXC(1);
#endif
}
/*
* convert the python PyObject to a postgresql Datum FIXME returning a
* NULL, ie PG_RETURN_NULL() blows the backend to small messy bits...
* it this a bug or expected? so just call with the string value of
* None for now
*/
if (plrv == Py_None)
{
fcinfo->isnull = true;
rv = (Datum) NULL;
}
else
{
fcinfo->isnull = false;
plrv_so = PyObject_Str(plrv);
plrv_sc = PyString_AsString(plrv_so);
rv = FunctionCall3(&proc->result.out.d.typfunc,
PointerGetDatum(plrv_sc),
ObjectIdGetDatum(proc->result.out.d.typelem),
Int32GetDatum(-1));
}
RESTORE_EXC();
Py_XDECREF(plargs);
Py_DECREF(plrv);
Py_XDECREF(plrv_so);
return rv;
}
PyObject *
PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
{
PyObject *rv;
PLyProcedure *current;
enter();
current = PLy_last_procedure;
PLy_last_procedure = proc;
PyDict_SetItemString(proc->globals, kargs, vargs);
rv = PyEval_EvalCode((PyCodeObject *) proc->code, proc->globals, proc->globals);
PLy_last_procedure = current;
if ((rv == NULL) || (PyErr_Occurred()))
{
Py_XDECREF(rv);
if (!PLy_restart_in_progress)
PLy_elog(ERROR, "function \"%s\" failed", proc->proname);
RAISE_EXC(1);
}
return rv;
}
PyObject *
PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
{
DECLARE_EXC();
PyObject *volatile arg = NULL;
PyObject *volatile args = NULL;
int i;
enter();
/*
* FIXME -- if the setjmp setup is expensive, add the arg and args
* field to the procedure struct and cleanup at the start of the next
* call
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(arg);
Py_XDECREF(args);
RERAISE_EXC();
}
args = PyList_New(proc->nargs);
for (i = 0; i < proc->nargs; i++)
{
if (proc->args[i].is_rowtype > 0)
{
if (fcinfo->argnull[i])
arg = NULL;
else
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
td = DatumGetHeapTupleHeader(fcinfo->arg[i]);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Set up I/O funcs if not done yet */
if (proc->args[i].is_rowtype != 1)
PLy_input_tuple_funcs(&(proc->args[i]), tupdesc);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
tmptup.t_data = td;
arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc);
}
}
else
{
if (fcinfo->argnull[i])
arg = NULL;
else
{
char *ct;
Datum dt;
dt = FunctionCall3(&(proc->args[i].in.d.typfunc),
fcinfo->arg[i],
ObjectIdGetDatum(proc->args[i].in.d.typelem),
Int32GetDatum(-1));
ct = DatumGetCString(dt);
arg = (proc->args[i].in.d.func) (ct);
pfree(ct);
}
}
if (arg == NULL)
{
Py_INCREF(Py_None);
arg = Py_None;
}
/*
* FIXME -- error check this
*/
PyList_SetItem(args, i, arg);
}
RESTORE_EXC();
return args;
}
/*
* PLyProcedure functions
*/
/* PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and
* returns a new PLyProcedure. fcinfo is the call info, tgreloid is the
* relation OID when calling a trigger, or InvalidOid (zero) for ordinary
* function calls.
*/
static PLyProcedure *
PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid)
{
Oid fn_oid;
HeapTuple procTup;
char key[128];
PyObject *plproc;
PLyProcedure *proc = NULL;
int rv;
enter();
fn_oid = fcinfo->flinfo->fn_oid;
procTup = SearchSysCache(PROCOID,
ObjectIdGetDatum(fn_oid),
0, 0, 0);
if (!HeapTupleIsValid(procTup))
elog(ERROR, "cache lookup failed for function %u", fn_oid);
rv = snprintf(key, sizeof(key), "%u_%u", fn_oid, tgreloid);
if ((rv >= sizeof(key)) || (rv < 0))
elog(ERROR, "key too long");
plproc = PyDict_GetItemString(PLy_procedure_cache, key);
if (plproc != NULL)
{
Py_INCREF(plproc);
if (!PyCObject_Check(plproc))
elog(FATAL, "expected a PyCObject, didn't get one");
mark();
proc = PyCObject_AsVoidPtr(plproc);
if (proc->me != plproc)
elog(FATAL, "proc->me != plproc");
/* did we find an up-to-date cache entry? */
if (proc->fn_xmin != HeapTupleHeaderGetXmin(procTup->t_data) ||
proc->fn_cmin != HeapTupleHeaderGetCmin(procTup->t_data))
{
Py_DECREF(plproc);
proc = NULL;
}
}
if (proc == NULL)
proc = PLy_procedure_create(fcinfo, tgreloid, procTup, key);
ReleaseSysCache(procTup);
return proc;
}
static PLyProcedure *
PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
HeapTuple procTup, char *key)
{
char procName[NAMEDATALEN + 256];
DECLARE_EXC();
Form_pg_proc procStruct;
PLyProcedure *volatile proc;
char *volatile procSource = NULL;
Datum prosrcdatum;
bool isnull;
int i,
rv;
enter();
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
if (OidIsValid(tgreloid))
rv = snprintf(procName, sizeof(procName),
"__plpython_procedure_%s_%u_trigger_%u",
NameStr(procStruct->proname),
fcinfo->flinfo->fn_oid,
tgreloid);
else
rv = snprintf(procName, sizeof(procName),
"__plpython_procedure_%s_%u",
NameStr(procStruct->proname),
fcinfo->flinfo->fn_oid);
if ((rv >= sizeof(procName)) || (rv < 0))
elog(ERROR, "procedure name would overrun buffer");
proc = PLy_malloc(sizeof(PLyProcedure));
proc->proname = PLy_malloc(strlen(NameStr(procStruct->proname)) + 1);
strcpy(proc->proname, NameStr(procStruct->proname));
proc->pyname = PLy_malloc(strlen(procName) + 1);
strcpy(proc->pyname, procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
PLy_typeinfo_init(&proc->result);
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
proc->code = proc->statics = NULL;
proc->globals = proc->me = NULL;
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
PLy_procedure_delete(proc);
if (procSource)
pfree(procSource);
RERAISE_EXC();
}
/*
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
if (!CALLED_AS_TRIGGER(fcinfo))
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
rvTypeTup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(procStruct->prorettype),
0, 0, 0);
if (!HeapTupleIsValid(rvTypeTup))
elog(ERROR, "cache lookup failed for type %u",
procStruct->prorettype);
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
if (rvTypeStruct->typtype != 'c')
PLy_output_datum_func(&proc->result, rvTypeStruct);
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("tuple return types are not supported yet")));
ReleaseSysCache(rvTypeTup);
}
else
{
/*
* input/output conversion for trigger tuples. use the result
* TypeInfo variable to store the tuple conversion info.
*/
TriggerData *tdata = (TriggerData *) fcinfo->context;
PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
}
/*
* now get information required for input conversion of the procedures
* arguments.
*/
proc->nargs = fcinfo->nargs;
for (i = 0; i < fcinfo->nargs; i++)
{
HeapTuple argTypeTup;
Form_pg_type argTypeStruct;
argTypeTup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(procStruct->proargtypes[i]),
0, 0, 0);
if (!HeapTupleIsValid(argTypeTup))
elog(ERROR, "cache lookup failed for type %u",
procStruct->proargtypes[i]);
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
if (argTypeStruct->typtype != 'c')
PLy_input_datum_func(&(proc->args[i]),
procStruct->proargtypes[i],
argTypeStruct);
else
proc->args[i].is_rowtype = 2; /* still need to set I/O funcs */
ReleaseSysCache(argTypeTup);
}
/*
* get the text of the function.
*/
prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null prosrc");
procSource = DatumGetCString(DirectFunctionCall1(textout,
prosrcdatum));
PLy_procedure_compile(proc, procSource);
pfree(procSource);
proc->me = PyCObject_FromVoidPtr(proc, NULL);
PyDict_SetItemString(PLy_procedure_cache, key, proc->me);
RESTORE_EXC();
return proc;
}
void
PLy_procedure_compile(PLyProcedure * proc, const char *src)
{
PyObject *crv = NULL;
char *msrc;
enter();
proc->globals = PyDict_Copy(PLy_interp_globals);
/*
* SD is private preserved data between calls GD is global data shared
* by all functions
*/
proc->statics = PyDict_New();
PyDict_SetItemString(proc->globals, "SD", proc->statics);
/*
* insert the function code into the interpreter
*/
msrc = PLy_procedure_munge_source(proc->pyname, src);
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
free(msrc);
if ((crv != NULL) && (!PyErr_Occurred()))
{
int clen;
char call[NAMEDATALEN + 256];
Py_DECREF(crv);
/*
* compile a call to the function
*/
clen = snprintf(call, sizeof(call), "%s()", proc->pyname);
if ((clen < 0) || (clen >= sizeof(call)))
elog(ERROR, "string would overflow buffer");
proc->code = Py_CompileString(call, "<string>", Py_eval_input);
if ((proc->code != NULL) && (!PyErr_Occurred()))
return;
}
else
Py_XDECREF(crv);
PLy_elog(ERROR, "could not compile function \"%s\"", proc->proname);
}
char *
PLy_procedure_munge_source(const char *name, const char *src)
{
char *mrc,
*mp;
const char *sp;
size_t mlen,
plen;
enter();
/*
* room for function source and the def statement
*/
mlen = (strlen(src) * 2) + strlen(name) + 16;
mrc = PLy_malloc(mlen);
plen = snprintf(mrc, mlen, "def %s():\n\t", name);
Assert(plen >= 0 && plen < mlen);
sp = src;
mp = mrc + plen;
while (*sp != '\0')
{
if (*sp == '\n')
{
*mp++ = *sp++;
*mp++ = '\t';
}
else
*mp++ = *sp++;
}
*mp++ = '\n';
*mp++ = '\n';
*mp = '\0';
if (mp > (mrc + mlen))
elog(FATAL, "buffer overrun in PLy_munge_source");
return mrc;
}
void
PLy_procedure_delete(PLyProcedure * proc)
{
int i;
enter();
Py_XDECREF(proc->code);
Py_XDECREF(proc->statics);
Py_XDECREF(proc->globals);
Py_XDECREF(proc->me);
if (proc->proname)
PLy_free(proc->proname);
if (proc->pyname)
PLy_free(proc->pyname);
for (i = 0; i < proc->nargs; i++)
if (proc->args[i].is_rowtype == 1)
{
if (proc->args[i].in.r.atts)
PLy_free(proc->args[i].in.r.atts);
if (proc->args[i].out.r.atts)
PLy_free(proc->args[i].out.r.atts);
}
leave();
}
/* conversion functions. remember output from python is
* input to postgresql, and vis versa.
*/
void
PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc)
{
int i;
enter();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
arg->is_rowtype = 1;
arg->in.r.natts = desc->natts;
arg->in.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb));
for (i = 0; i < desc->natts; i++)
{
HeapTuple typeTup;
Form_pg_type typeStruct;
if (desc->attrs[i]->attisdropped)
continue;
typeTup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(desc->attrs[i]->atttypid),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u",
desc->attrs[i]->atttypid);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLy_input_datum_func2(&(arg->in.r.atts[i]),
desc->attrs[i]->atttypid,
typeStruct);
ReleaseSysCache(typeTup);
}
}
void
PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc)
{
int i;
enter();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
arg->is_rowtype = 1;
arg->out.r.natts = desc->natts;
arg->out.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb));
for (i = 0; i < desc->natts; i++)
{
HeapTuple typeTup;
Form_pg_type typeStruct;
if (desc->attrs[i]->attisdropped)
continue;
typeTup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(desc->attrs[i]->atttypid),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u",
desc->attrs[i]->atttypid);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLy_output_datum_func2(&(arg->out.r.atts[i]), typeStruct);
ReleaseSysCache(typeTup);
}
}
void
PLy_output_datum_func(PLyTypeInfo * arg, Form_pg_type typeStruct)
{
enter();
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
arg->is_rowtype = 0;
PLy_output_datum_func2(&(arg->out.d), typeStruct);
}
void
PLy_output_datum_func2(PLyObToDatum * arg, Form_pg_type typeStruct)
{
enter();
perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
arg->typelem = typeStruct->typelem;
arg->typbyval = typeStruct->typbyval;
}
void
PLy_input_datum_func(PLyTypeInfo * arg, Oid typeOid, Form_pg_type typeStruct)
{
enter();
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
arg->is_rowtype = 0;
PLy_input_datum_func2(&(arg->in.d), typeOid, typeStruct);
}
void
PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, Form_pg_type typeStruct)
{
/* Get the type's conversion information */
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
arg->typelem = typeStruct->typelem;
arg->typbyval = typeStruct->typbyval;
/* Determine which kind of Python object we will convert to */
switch (typeOid)
{
case BOOLOID:
arg->func = PLyBool_FromString;
break;
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
arg->func = PLyFloat_FromString;
break;
case INT2OID:
case INT4OID:
arg->func = PLyInt_FromString;
break;
case INT8OID:
arg->func = PLyLong_FromString;
break;
default:
arg->func = PLyString_FromString;
break;
}
}
void
PLy_typeinfo_init(PLyTypeInfo * arg)
{
arg->is_rowtype = -1;
arg->in.r.natts = arg->out.r.natts = 0;
arg->in.r.atts = NULL;
arg->out.r.atts = NULL;
}
void
PLy_typeinfo_dealloc(PLyTypeInfo * arg)
{
if (arg->is_rowtype == 1)
{
if (arg->in.r.atts)
PLy_free(arg->in.r.atts);
if (arg->out.r.atts)
PLy_free(arg->out.r.atts);
}
}
/* assumes that a bool is always returned as a 't' or 'f'
*/
PyObject *
PLyBool_FromString(const char *src)
{
enter();
if (src[0] == 't')
return PyInt_FromLong(1);
return PyInt_FromLong(0);
}
PyObject *
PLyFloat_FromString(const char *src)
{
double v;
char *eptr;
enter();
errno = 0;
v = strtod(src, &eptr);
if ((*eptr != '\0') || (errno))
return NULL;
return PyFloat_FromDouble(v);
}
PyObject *
PLyInt_FromString(const char *src)
{
long v;
char *eptr;
enter();
errno = 0;
v = strtol(src, &eptr, 0);
if ((*eptr != '\0') || (errno))
return NULL;
return PyInt_FromLong(v);
}
PyObject *
PLyLong_FromString(const char *src)
{
return PyLong_FromString((char *) src, NULL, 0);
}
PyObject *
PLyString_FromString(const char *src)
{
return PyString_FromString(src);
}
PyObject *
PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc)
{
DECLARE_EXC();
PyObject *volatile dict;
int i;
enter();
if (info->is_rowtype != 1)
elog(ERROR, "PLyTypeInfo structure describes a datum");
dict = PyDict_New();
if (dict == NULL)
PLy_elog(ERROR, "could not create tuple dictionary");
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_DECREF(dict);
RERAISE_EXC();
}
for (i = 0; i < info->in.r.natts; i++)
{
char *key,
*vsrc;
Datum vattr,
vdat;
bool is_null;
PyObject *value;
if (desc->attrs[i]->attisdropped)
continue;
key = NameStr(desc->attrs[i]->attname);
vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
if ((is_null) || (info->in.r.atts[i].func == NULL))
PyDict_SetItemString(dict, key, Py_None);
else
{
vdat = FunctionCall3(&info->in.r.atts[i].typfunc,
vattr,
ObjectIdGetDatum(info->in.r.atts[i].typelem),
Int32GetDatum(desc->attrs[i]->atttypmod));
vsrc = DatumGetCString(vdat);
/*
* no exceptions allowed
*/
value = info->in.r.atts[i].func(vsrc);
pfree(vsrc);
PyDict_SetItemString(dict, key, value);
Py_DECREF(value);
}
}
RESTORE_EXC();
return dict;
}
/* initialization, some python variables function declared here
*/
/* interface to postgresql elog
*/
static PyObject *PLy_debug(PyObject *, PyObject *);
static PyObject *PLy_log(PyObject *, PyObject *);
static PyObject *PLy_info(PyObject *, PyObject *);
static PyObject *PLy_notice(PyObject *, PyObject *);
static PyObject *PLy_warning(PyObject *, PyObject *);
static PyObject *PLy_error(PyObject *, PyObject *);
static PyObject *PLy_fatal(PyObject *, PyObject *);
/* PLyPlanObject, PLyResultObject and SPI interface
*/
#define is_PLyPlanObject(x) ((x)->ob_type == &PLy_PlanType)
static PyObject *PLy_plan_new(void);
static void PLy_plan_dealloc(PyObject *);
static PyObject *PLy_plan_getattr(PyObject *, char *);
static PyObject *PLy_plan_status(PyObject *, PyObject *);
static PyObject *PLy_result_new(void);
static void PLy_result_dealloc(PyObject *);
static PyObject *PLy_result_getattr(PyObject *, char *);
#ifdef NOT_USED
/* Appear to be unused */
static PyObject *PLy_result_fetch(PyObject *, PyObject *);
static PyObject *PLy_result_nrows(PyObject *, PyObject *);
static PyObject *PLy_result_status(PyObject *, PyObject *);
#endif
static int PLy_result_length(PyObject *);
static PyObject *PLy_result_item(PyObject *, int);
static PyObject *PLy_result_slice(PyObject *, int, int);
static int PLy_result_ass_item(PyObject *, int, PyObject *);
static int PLy_result_ass_slice(PyObject *, int, int, PyObject *);
static PyObject *PLy_spi_prepare(PyObject *, PyObject *);
static PyObject *PLy_spi_execute(PyObject *, PyObject *);
static const char *PLy_spi_error_string(int);
static PyObject *PLy_spi_execute_query(char *query, int limit);
static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, int);
static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int);
static PyTypeObject PLy_PlanType = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
"PLyPlan", /* tp_name */
sizeof(PLyPlanObject), /* tp_size */
0, /* tp_itemsize */
/*
* methods
*/
(destructor) PLy_plan_dealloc, /* tp_dealloc */
0, /* tp_print */
(getattrfunc) PLy_plan_getattr, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
0, /* tp_xxx4 */
PLy_plan_doc, /* tp_doc */
};
static PyMethodDef PLy_plan_methods[] = {
{"status", (PyCFunction) PLy_plan_status, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
static PySequenceMethods PLy_result_as_sequence = {
(inquiry) PLy_result_length, /* sq_length */
(binaryfunc) 0, /* sq_concat */
(intargfunc) 0, /* sq_repeat */
(intargfunc) PLy_result_item, /* sq_item */
(intintargfunc) PLy_result_slice, /* sq_slice */
(intobjargproc) PLy_result_ass_item, /* sq_ass_item */
(intintobjargproc) PLy_result_ass_slice, /* sq_ass_slice */
};
static PyTypeObject PLy_ResultType = {
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
"PLyResult", /* tp_name */
sizeof(PLyResultObject), /* tp_size */
0, /* tp_itemsize */
/*
* methods
*/
(destructor) PLy_result_dealloc, /* tp_dealloc */
0, /* tp_print */
(getattrfunc) PLy_result_getattr, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
&PLy_result_as_sequence, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
0, /* tp_xxx4 */
PLy_result_doc, /* tp_doc */
};
#ifdef NOT_USED
/* Appear to be unused */
static PyMethodDef PLy_result_methods[] = {
{"fetch", (PyCFunction) PLy_result_fetch, METH_VARARGS, NULL,},
{"nrows", (PyCFunction) PLy_result_nrows, METH_VARARGS, NULL},
{"status", (PyCFunction) PLy_result_status, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
#endif
static PyMethodDef PLy_methods[] = {
/*
* logging methods
*/
{"debug", PLy_debug, METH_VARARGS, NULL},
{"log", PLy_log, METH_VARARGS, NULL},
{"info", PLy_info, METH_VARARGS, NULL},
{"notice", PLy_notice, METH_VARARGS, NULL},
{"warning", PLy_warning, METH_VARARGS, NULL},
{"error", PLy_error, METH_VARARGS, NULL},
{"fatal", PLy_fatal, METH_VARARGS, NULL},
/*
* create a stored plan
*/
{"prepare", PLy_spi_prepare, METH_VARARGS, NULL},
/*
* execute a plan or query
*/
{"execute", PLy_spi_execute, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
/* plan object methods
*/
PyObject *
PLy_plan_new(void)
{
PLyPlanObject *ob;
enter();
if ((ob = PyObject_NEW(PLyPlanObject, &PLy_PlanType)) == NULL)
return NULL;
ob->plan = NULL;
ob->nargs = 0;
ob->types = NULL;
ob->args = NULL;
return (PyObject *) ob;
}
void
PLy_plan_dealloc(PyObject * arg)
{
PLyPlanObject *ob = (PLyPlanObject *) arg;
enter();
if (ob->plan)
SPI_freeplan(ob->plan);
if (ob->types)
PLy_free(ob->types);
if (ob->args)
{
int i;
for (i = 0; i < ob->nargs; i++)
PLy_typeinfo_dealloc(&ob->args[i]);
PLy_free(ob->args);
}
PyMem_DEL(arg);
leave();
}
PyObject *
PLy_plan_getattr(PyObject * self, char *name)
{
return Py_FindMethod(PLy_plan_methods, self, name);
}
PyObject *
PLy_plan_status(PyObject * self, PyObject * args)
{
if (PyArg_ParseTuple(args, ""))
{
Py_INCREF(Py_True);
return Py_True;
/* return PyInt_FromLong(self->status); */
}
PyErr_SetString(PLy_exc_error, "plan.status() takes no arguments");
return NULL;
}
/* result object methods
*/
PyObject *
PLy_result_new(void)
{
PLyResultObject *ob;
enter();
if ((ob = PyObject_NEW(PLyResultObject, &PLy_ResultType)) == NULL)
return NULL;
/* ob->tuples = NULL; */
Py_INCREF(Py_None);
ob->status = Py_None;
ob->nrows = PyInt_FromLong(-1);
ob->rows = PyList_New(0);
return (PyObject *) ob;
}
void
PLy_result_dealloc(PyObject * arg)
{
PLyResultObject *ob = (PLyResultObject *) arg;
enter();
Py_XDECREF(ob->nrows);
Py_XDECREF(ob->rows);
Py_XDECREF(ob->status);
PyMem_DEL(ob);
}
PyObject *
PLy_result_getattr(PyObject * self, char *attr)
{
return NULL;
}
#ifdef NOT_USED
/* Appear to be unused */
PyObject *
PLy_result_fetch(PyObject * self, PyObject * args)
{
return NULL;
}
PyObject *
PLy_result_nrows(PyObject * self, PyObject * args)
{
PLyResultObject *ob = (PLyResultObject *) self;
Py_INCREF(ob->nrows);
return ob->nrows;
}
PyObject *
PLy_result_status(PyObject * self, PyObject * args)
{
PLyResultObject *ob = (PLyResultObject *) self;
Py_INCREF(ob->status);
return ob->status;
}
#endif
int
PLy_result_length(PyObject * arg)
{
PLyResultObject *ob = (PLyResultObject *) arg;
return PyList_Size(ob->rows);
}
PyObject *
PLy_result_item(PyObject * arg, int idx)
{
PyObject *rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_GetItem(ob->rows, idx);
if (rv != NULL)
Py_INCREF(rv);
return rv;
}
int
PLy_result_ass_item(PyObject * arg, int idx, PyObject * item)
{
int rv;
PLyResultObject *ob = (PLyResultObject *) arg;
Py_INCREF(item);
rv = PyList_SetItem(ob->rows, idx, item);
return rv;
}
PyObject *
PLy_result_slice(PyObject * arg, int lidx, int hidx)
{
PyObject *rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_GetSlice(ob->rows, lidx, hidx);
if (rv == NULL)
return NULL;
Py_INCREF(rv);
return rv;
}
int
PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice)
{
int rv;
PLyResultObject *ob = (PLyResultObject *) arg;
rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
return rv;
}
/* SPI interface
*/
PyObject *
PLy_spi_prepare(PyObject * self, PyObject * args)
{
DECLARE_EXC();
PLyPlanObject *plan;
PyObject *list = NULL;
PyObject *volatile optr = NULL;
char *query;
void *tmpplan;
enter();
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
{
PyErr_SetString(PLy_exc_spi_error,
"Invalid arguments for plpy.prepare()");
return NULL;
}
if ((list) && (!PySequence_Check(list)))
{
PyErr_SetString(PLy_exc_spi_error,
"Second argument in plpy.prepare() must be a sequence");
return NULL;
}
if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
return NULL;
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_DECREF(plan);
Py_XDECREF(optr);
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare");
/* XXX this oughta be replaced with errcontext mechanism */
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
RERAISE_EXC();
}
if (list != NULL)
{
int nargs,
i;
nargs = PySequence_Length(list);
if (nargs > 0)
{
plan->nargs = nargs;
plan->types = PLy_malloc(sizeof(Oid) * nargs);
plan->values = PLy_malloc(sizeof(Datum) * nargs);
plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs);
/*
* the other loop might throw an exception, if PLyTypeInfo
* member isn't properly initialized the Py_DECREF(plan) will
* go boom
*/
for (i = 0; i < nargs; i++)
{
PLy_typeinfo_init(&plan->args[i]);
plan->values[i] = (Datum) NULL;
}
for (i = 0; i < nargs; i++)
{
char *sptr;
HeapTuple typeTup;
Form_pg_type typeStruct;
optr = PySequence_GetItem(list, i);
if (!PyString_Check(optr))
{
PyErr_SetString(PLy_exc_spi_error,
"Type names must be strings.");
RAISE_EXC(1);
}
sptr = PyString_AsString(optr);
/* XXX should extend this to allow qualified type names */
typeTup = typenameType(makeTypeName(sptr));
Py_DECREF(optr);
optr = NULL; /* this is important */
plan->types[i] = HeapTupleGetOid(typeTup);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
if (typeStruct->typtype != 'c')
PLy_output_datum_func(&plan->args[i], typeStruct);
else
{
PyErr_SetString(PLy_exc_spi_error,
"tuples not handled in plpy.prepare, yet.");
RAISE_EXC(1);
}
ReleaseSysCache(typeTup);
}
}
}
plan->plan = SPI_prepare(query, plan->nargs, plan->types);
if (plan->plan == NULL)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to prepare plan. SPI_prepare failed -- %s.",
PLy_spi_error_string(SPI_result));
RAISE_EXC(1);
}
/* transfer plan from procCxt to topCxt */
tmpplan = plan->plan;
plan->plan = SPI_saveplan(tmpplan);
SPI_freeplan(tmpplan);
if (plan->plan == NULL)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to save plan. SPI_saveplan failed -- %s.",
PLy_spi_error_string(SPI_result));
RAISE_EXC(1);
}
RESTORE_EXC();
return (PyObject *) plan;
}
/* execute(query="select * from foo", limit=5)
* execute(plan=plan, values=(foo, bar), limit=5)
*/
PyObject *
PLy_spi_execute(PyObject * self, PyObject * args)
{
char *query;
PyObject *plan;
PyObject *list = NULL;
int limit = 0;
enter();
#ifdef NOT_USED
/*
* there should - hahaha - be an python exception set so just return
* NULL. FIXME -- is this needed?
*/
if (PLy_restart_in_progress)
return NULL;
#endif
if (PyArg_ParseTuple(args, "s|i", &query, &limit))
return PLy_spi_execute_query(query, limit);
PyErr_Clear();
if ((PyArg_ParseTuple(args, "O|Oi", &plan, &list, &limit)) &&
(is_PLyPlanObject(plan)))
{
PyObject *rv = PLy_spi_execute_plan(plan, list, limit);
return rv;
}
PyErr_SetString(PLy_exc_error, "Expected a query or plan.");
return NULL;
}
PyObject *
PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
{
DECLARE_EXC();
volatile int nargs;
int i,
rv;
PLyPlanObject *plan;
char *nulls;
enter();
if (list != NULL)
{
if ((!PySequence_Check(list)) || (PyString_Check(list)))
{
char *msg = "plpy.execute() takes a sequence as its second argument";
PyErr_SetString(PLy_exc_spi_error, msg);
return NULL;
}
nargs = PySequence_Length(list);
}
else
nargs = 0;
plan = (PLyPlanObject *) ob;
if (nargs != plan->nargs)
{
char *sv;
PyObject *so = PyObject_Str(list);
sv = PyString_AsString(so);
PLy_exception_set(PLy_exc_spi_error,
"Expected sequence of %d arguments, got %d. %s",
plan->nargs, nargs, sv);
Py_DECREF(so);
return NULL;
}
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
/*
* cleanup plan->values array
*/
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].out.d.typbyval &&
(plan->values[i] != (Datum) NULL))
{
pfree(DatumGetPointer(plan->values[i]));
plan->values[i] = (Datum) NULL;
}
}
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
RERAISE_EXC();
}
if (nargs)
{
nulls = palloc((nargs + 1) * sizeof(char));
for (i = 0; i < nargs; i++)
{
PyObject *elem,
*so;
char *sv;
elem = PySequence_GetItem(list, i);
if (elem != Py_None)
{
so = PyObject_Str(elem);
sv = PyString_AsString(so);
/*
* FIXME -- if this can elog, we have leak
*/
plan->values[i] = FunctionCall3(&(plan->args[i].out.d.typfunc),
CStringGetDatum(sv),
ObjectIdGetDatum(plan->args[i].out.d.typelem),
Int32GetDatum(-1));
Py_DECREF(so);
Py_DECREF(elem);
nulls[i] = ' ';
}
else
{
Py_DECREF(elem);
plan->values[i] = (Datum) 0;
nulls[i] = 'n';
}
}
nulls[i] = '\0';
}
else
nulls = NULL;
rv = SPI_execp(plan->plan, plan->values, nulls, limit);
RESTORE_EXC();
for (i = 0; i < nargs; i++)
{
if (!plan->args[i].out.d.typbyval &&
(plan->values[i] != (Datum) NULL))
{
pfree(DatumGetPointer(plan->values[i]));
plan->values[i] = (Datum) NULL;
}
}
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to execute plan. SPI_execp failed -- %s",
PLy_spi_error_string(rv));
return NULL;
}
return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
PyObject *
PLy_spi_execute_query(char *query, int limit)
{
DECLARE_EXC();
int rv;
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
if ((!PLy_restart_in_progress) && (!PyErr_Occurred()))
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query");
PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
RERAISE_EXC();
}
rv = SPI_exec(query, limit);
RESTORE_EXC();
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
"Unable to execute query. SPI_exec failed -- %s",
PLy_spi_error_string(rv));
return NULL;
}
return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
PyObject *
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
{
PLyResultObject *result;
enter();
result = (PLyResultObject *) PLy_result_new();
Py_DECREF(result->status);
result->status = PyInt_FromLong(status);
if (status == SPI_OK_UTILITY)
{
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(0);
}
else if (status != SPI_OK_SELECT)
{
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(rows);
}
else
{
DECLARE_EXC();
PLyTypeInfo args;
int i;
PLy_typeinfo_init(&args);
Py_DECREF(result->nrows);
result->nrows = PyInt_FromLong(rows);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_fetch_result");
Py_DECREF(result);
PLy_typeinfo_dealloc(&args);
RERAISE_EXC();
}
if (rows)
{
Py_DECREF(result->rows);
result->rows = PyList_New(rows);
PLy_input_tuple_funcs(&args, tuptable->tupdesc);
for (i = 0; i < rows; i++)
{
PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i],
tuptable->tupdesc);
PyList_SetItem(result->rows, i, row);
}
PLy_typeinfo_dealloc(&args);
SPI_freetuptable(tuptable);
}
RESTORE_EXC();
}
return (PyObject *) result;
}
const char *
PLy_spi_error_string(int code)
{
switch (code)
{
case SPI_ERROR_TYPUNKNOWN:
return "SPI_ERROR_TYPUNKNOWN";
case SPI_ERROR_NOOUTFUNC:
return "SPI_ERROR_NOOUTFUNC";
case SPI_ERROR_NOATTRIBUTE:
return "SPI_ERROR_NOATTRIBUTE";
case SPI_ERROR_TRANSACTION:
return "SPI_ERROR_TRANSACTION";
case SPI_ERROR_PARAM:
return "SPI_ERROR_PARAM";
case SPI_ERROR_ARGUMENT:
return "SPI_ERROR_ARGUMENT";
case SPI_ERROR_CURSOR:
return "SPI_ERROR_CURSOR";
case SPI_ERROR_UNCONNECTED:
return "SPI_ERROR_UNCONNECTED";
case SPI_ERROR_OPUNKNOWN:
return "SPI_ERROR_OPUNKNOWN";
case SPI_ERROR_COPY:
return "SPI_ERROR_COPY";
case SPI_ERROR_CONNECT:
return "SPI_ERROR_CONNECT";
}
return "Unknown or Invalid code";
}
/* language handler and interpreter initialization
*/
/*
* plpython_init() - Initialize everything that can be
* safely initialized during postmaster
* startup.
*
* DO NOT make this static --- it has to be callable by preload
*/
void
plpython_init(void)
{
static volatile int init_active = 0;
/* Do initialization only once */
if (!PLy_first_call)
return;
enter();
if (init_active)
elog(FATAL, "initialization of language module failed");
init_active = 1;
Py_Initialize();
PLy_init_interp();
PLy_init_plpy();
if (PyErr_Occurred())
PLy_elog(FATAL, "untrapped error in initialization");
PLy_procedure_cache = PyDict_New();
if (PLy_procedure_cache == NULL)
PLy_elog(ERROR, "could not create procedure cache");
PLy_first_call = 0;
leave();
}
static void
PLy_init_all(void)
{
/* Execute postmaster-startup safe initialization */
if (PLy_first_call)
plpython_init();
/*
* Any other initialization that must be done each time a new backend
* starts -- currently none
*/
}
void
PLy_init_interp(void)
{
PyObject *mainmod;
enter();
mainmod = PyImport_AddModule("__main__");
if ((mainmod == NULL) || (PyErr_Occurred()))
PLy_elog(ERROR, "could not import \"__main__\" module.");
Py_INCREF(mainmod);
PLy_interp_globals = PyModule_GetDict(mainmod);
PLy_interp_safe_globals = PyDict_New();
PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
Py_DECREF(mainmod);
if ((PLy_interp_globals == NULL) || (PyErr_Occurred()))
PLy_elog(ERROR, "could not initialize globals");
}
void
PLy_init_plpy(void)
{
PyObject *main_mod,
*main_dict,
*plpy_mod;
PyObject *plpy,
*plpy_dict;
enter();
/*
* initialize plpy module
*/
PLy_PlanType.ob_type = PLy_ResultType.ob_type = &PyType_Type;
plpy = Py_InitModule("plpy", PLy_methods);
plpy_dict = PyModule_GetDict(plpy);
/* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL);
PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL);
PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL);
PyDict_SetItemString(plpy_dict, "Error", PLy_exc_error);
PyDict_SetItemString(plpy_dict, "Fatal", PLy_exc_fatal);
PyDict_SetItemString(plpy_dict, "SPIError", PLy_exc_spi_error);
/*
* initialize main module, and add plpy
*/
main_mod = PyImport_AddModule("__main__");
main_dict = PyModule_GetDict(main_mod);
plpy_mod = PyImport_AddModule("plpy");
PyDict_SetItemString(main_dict, "plpy", plpy_mod);
if (PyErr_Occurred())
elog(ERROR, "could not init plpy");
}
/* the python interface to the elog function
* don't confuse these with PLy_elog
*/
static PyObject *PLy_output(int, PyObject *, PyObject *);
PyObject *
PLy_debug(PyObject * self, PyObject * args)
{
return PLy_output(DEBUG2, self, args);
}
PyObject *
PLy_log(PyObject * self, PyObject * args)
{
return PLy_output(LOG, self, args);
}
PyObject *
PLy_info(PyObject * self, PyObject * args)
{
return PLy_output(INFO, self, args);
}
PyObject *
PLy_notice(PyObject * self, PyObject * args)
{
return PLy_output(NOTICE, self, args);
}
PyObject *
PLy_warning(PyObject * self, PyObject * args)
{
return PLy_output(WARNING, self, args);
}
PyObject *
PLy_error(PyObject * self, PyObject * args)
{
return PLy_output(ERROR, self, args);
}
PyObject *
PLy_fatal(PyObject * self, PyObject * args)
{
return PLy_output(FATAL, self, args);
}
PyObject *
PLy_output(volatile int level, PyObject * self, PyObject * args)
{
DECLARE_EXC();
PyObject *so;
char *volatile sv;
enter();
if (args == NULL)
elog(WARNING, "args is NULL");
so = PyObject_Str(args);
if ((so == NULL) || ((sv = PyString_AsString(so)) == NULL))
{
level = ERROR;
sv = "Unable to parse error message in `plpy.elog'";
}
/*
* returning NULL here causes the python interpreter to bail. when
* control passes back into plpython_*_handler, we check for python
* exceptions and do the actual elog call. actually PLy_elog.
*/
if (level == ERROR)
{
PyErr_SetString(PLy_exc_error, sv);
return NULL;
}
else if (level >= FATAL)
{
PyErr_SetString(PLy_exc_fatal, sv);
return NULL;
}
/*
* ok, this is a WARNING, or LOG message
*
* but just in case DON'T long jump out of the interpreter!
*/
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
Py_XDECREF(so);
/*
* the real error message should already be written into the
* postgresql log, no? whatever, this shouldn't happen so die
* hideously.
*/
elog(FATAL, "elog threw an unknown exception");
RERAISE_EXC();
}
elog(level, "%s", sv);
RESTORE_EXC();
Py_XDECREF(so);
Py_INCREF(Py_None);
/*
* return a legal object so the interpreter will continue on its merry
* way
*/
return Py_None;
}
/*
* Get the last procedure name called by the backend ( the innermost,
* If a plpython procedure call calls the backend and the backend calls
* another plpython procedure )
*
* NB: this returns SQL name, not the internal Python procedure name
*/
char *
PLy_procedure_name(PLyProcedure * proc)
{
if (proc == NULL)
return "<unknown procedure>";
return proc->proname;
}
/* output a python traceback/exception via the postgresql elog
* function. not pretty.
*/
static char *PLy_traceback(int *);
static char *PLy_vprintf(const char *fmt, va_list ap);
static char *PLy_printf(const char *fmt,...);
void
PLy_exception_set(PyObject * exc, const char *fmt,...)
{
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
PyErr_SetString(exc, buf);
}
void
PLy_elog(int elevel, const char *fmt,...)
{
DECLARE_EXC();
va_list ap;
char *xmsg,
*emsg;
int xlevel;
enter();
xmsg = PLy_traceback(&xlevel);
va_start(ap, fmt);
emsg = PLy_vprintf(fmt, ap);
va_end(ap);
SAVE_EXC();
if (TRAP_EXC())
{
RESTORE_EXC();
mark();
/*
* elog called siglongjmp. cleanup, restore and reraise
*/
PLy_restart_in_progress += 1;
PLy_free(emsg);
if (xmsg)
PLy_free(xmsg);
RERAISE_EXC();
}
ereport(elevel,
(errmsg("plpython: %s", emsg),
(xmsg) ? errdetail("%s", xmsg) : 0));
PLy_free(emsg);
if (xmsg)
PLy_free(xmsg);
leave();
RESTORE_EXC();
}
char *
PLy_traceback(int *xlevel)
{
PyObject *e,
*v,
*tb;
PyObject *eob,
*vob = NULL;
char *vstr,
*estr,
*xstr = NULL;
enter();
/*
* get the current exception
*/
PyErr_Fetch(&e, &v, &tb);
/*
* oops, no exception, return
*/
if (e == NULL)
{
*xlevel = WARNING;
return NULL;
}
PyErr_NormalizeException(&e, &v, &tb);
eob = PyObject_Str(e);
if ((v) && ((vob = PyObject_Str(v)) != NULL))
vstr = PyString_AsString(vob);
else
vstr = "Unknown";
estr = PyString_AsString(eob);
xstr = PLy_printf("%s: %s", estr, vstr);
Py_DECREF(eob);
Py_XDECREF(vob);
/*
* intuit an appropriate error level for based on the exception type
*/
if ((PLy_exc_error) && (PyErr_GivenExceptionMatches(e, PLy_exc_error)))
*xlevel = ERROR;
else if ((PLy_exc_fatal) && (PyErr_GivenExceptionMatches(e, PLy_exc_fatal)))
*xlevel = FATAL;
else
*xlevel = ERROR;
leave();
return xstr;
}
char *
PLy_printf(const char *fmt,...)
{
va_list ap;
char *emsg;
va_start(ap, fmt);
emsg = PLy_vprintf(fmt, ap);
va_end(ap);
return emsg;
}
char *
PLy_vprintf(const char *fmt, va_list ap)
{
size_t blen;
int bchar,
tries = 2;
char *buf;
blen = strlen(fmt) * 2;
if (blen < 256)
blen = 256;
buf = PLy_malloc(blen * sizeof(char));
while (1)
{
bchar = vsnprintf(buf, blen, fmt, ap);
if ((bchar > 0) && (bchar < blen))
return buf;
if (tries-- <= 0)
break;
if (blen > 0)
blen = bchar + 1;
else
blen *= 2;
buf = PLy_realloc(buf, blen);
}
PLy_free(buf);
return NULL;
}
/* python module code
*/
/* some dumb utility functions
*/
void *
PLy_malloc(size_t bytes)
{
void *ptr = malloc(bytes);
if (ptr == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
return ptr;
}
void *
PLy_realloc(void *optr, size_t bytes)
{
void *nptr = realloc(optr, bytes);
if (nptr == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
return nptr;
}
/* define this away
*/
void
PLy_free(void *ptr)
{
free(ptr);
}