X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fpl%2Fplpython%2Fplpython.c;h=345f3f65236619d6cd431fde70a9aaef4eabe709;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=2a4c45e6e56bd9568a11d58120a860e787452c69;hpb=3b04893ffc5d856ecc2dcf0e903b2fe956d9ba29;p=postgresql diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 2a4c45e6e5..345f3f6523 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,69 +1,77 @@ /********************************************************************** * 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 - * $Header: /cvsroot/pgsql/src/pl/plpython/plpython.c,v 1.35 2003/07/25 23:37:30 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.121 2009/06/04 18:33:08 tgl Exp $ * ********************************************************************* */ -#include "postgres.h" +#if defined(_MSC_VER) && defined(_DEBUG) +/* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ +#undef _DEBUG +/* Also hide away errcode, since we load Python.h before postgres.h */ +#define errcode __msvc_errcode +#include +#undef errcode +#define _DEBUG +#elif defined (_MSC_VER) +#define errcode __msvc_errcode +#include +#undef errcode +#else +#include +#endif -/* system stuff +/* + * Py_ssize_t compat for Python <= 2.4 + */ +#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; + +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#endif + +/* + * PyBool_FromLong is supported from 2.3. */ -#include -#include -#include -#include +#if PY_VERSION_HEX < 0x02030000 +#define PyBool_FromLong(x) PyInt_FromLong(x) +#endif + + +#include "postgres.h" + +/* system stuff */ #include #include -#include -#include -/* postgreSQL stuff - */ -#include "access/heapam.h" +/* postgreSQL stuff */ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" #include "executor/spi.h" +#include "funcapi.h" #include "fmgr.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "tcop/tcopprot.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/syscache.h" +#include "utils/typcache.h" + +/* define our text domain for translations */ +#undef TEXTDOMAIN +#define TEXTDOMAIN PG_TEXTDOMAIN("plpython") -#include #include #include -#include "plpython.h" + +PG_MODULE_MAGIC; /* convert Postgresql Datum or tuple into a PyObject. * input to Python. Tuples are converted to dictionary @@ -75,8 +83,9 @@ typedef PyObject *(*PLyDatumToObFunc) (const char *); typedef struct PLyDatumToOb { PLyDatumToObFunc func; - FmgrInfo typfunc; - Oid typelem; + FmgrInfo typfunc; /* The type's output function */ + Oid typoid; /* The OID of the type */ + Oid typioparam; bool typbyval; } PLyDatumToOb; @@ -97,8 +106,9 @@ typedef union PLyTypeInput */ typedef struct PLyObToDatum { - FmgrInfo typfunc; - Oid typelem; + FmgrInfo typfunc; /* The type's input function */ + Oid typoid; /* The OID of the type */ + Oid typioparam; bool typbyval; } PLyObToDatum; @@ -121,35 +131,42 @@ typedef struct PLyTypeInfo { PLyTypeInput in; PLyTypeOutput out; - int is_rel; + 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 - */ +/* 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 */ + ItemPointerData fn_tid; + bool fn_readonly; + PLyTypeInfo result; /* also used to store info for trigger tuple + * type */ + bool is_setof; /* true, if procedure returns result set */ + PyObject *setof; /* contents of result set. */ + char **argnames; /* Argument names */ 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 *globals; /* data saved across calls, global scope */ PyObject *me; /* PyCObject containing pointer to this * PLyProcedure */ } PLyProcedure; -/* Python objects. - */ +/* Python objects */ typedef struct PLyPlanObject { - PyObject_HEAD; + PyObject_HEAD void *plan; /* return of an SPI_saveplan */ int nargs; Oid *types; @@ -159,62 +176,62 @@ typedef struct PLyPlanObject typedef struct PLyResultObject { - PyObject_HEAD; + PyObject_HEAD /* HeapTuple *tuples; */ - PyObject *nrows; /* number of rows returned by query */ + 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 - */ +/* function declarations */ -/* the only exported function, with the magic telling Postgresql - * what function call interface it implements. +/* Two exported functions: first is the magic telling Postgresql + * what function call interface it implements. Second is for + * initialization of the interpreter during library load. */ Datum plpython_call_handler(PG_FUNCTION_ARGS); +void _PG_init(void); PG_FUNCTION_INFO_V1(plpython_call_handler); -/* most of the remaining of the declarations, all static - */ +/* 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 *,...) +/* call PyErr_SetString with a vprint interface and translation support */ +static void PLy_exception_set(PyObject *, const char *,...) __attribute__((format(printf, 2, 3))); +/* same, with pluralized message */ +static void PLy_exception_set_plural(PyObject *, const char *, const char *, + unsigned long n,...) +__attribute__((format(printf, 2, 5))) +__attribute__((format(printf, 3, 5))); -/* Get the innermost python procedure called from the backend. - */ +/* Get the innermost python procedure called from the backend */ static char *PLy_procedure_name(PLyProcedure *); -/* some utility functions - */ +/* some utility functions */ +static void PLy_elog(int, const char *,...) +__attribute__((format(printf, 2, 3))); +static char *PLy_traceback(int *); + static void *PLy_malloc(size_t); -static void *PLy_realloc(void *, size_t); +static void *PLy_malloc0(size_t); +static char *PLy_strdup(const char *); static void PLy_free(void *); -/* sub handlers for functions and triggers - */ +/* 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 void PLy_function_delete_args(PLyProcedure *); static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *, HeapTuple *); static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *, @@ -222,14 +239,11 @@ static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *, static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *); -/* returns a cached PLyProcedure, or creates, stores and returns - * a new PLyProcedure. - */ -static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, bool); +static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, + Oid tgreloid); -static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo, - bool is_trigger, - HeapTuple procTup, char *key); +static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid tgreloid, + char *key); static void PLy_procedure_compile(PLyProcedure *, const char *); static char *PLy_procedure_munge_source(const char *, const char *); @@ -237,15 +251,14 @@ 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_datum_func(PLyTypeInfo *, HeapTuple); +static void PLy_output_datum_func2(PLyObToDatum *, HeapTuple); +static void PLy_input_datum_func(PLyTypeInfo *, Oid, HeapTuple); +static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple); static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc); static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); -/* conversion functions - */ +/* conversion functions */ static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); static PyObject *PLyBool_FromString(const char *); static PyObject *PLyFloat_FromString(const char *); @@ -253,35 +266,38 @@ 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; +static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *); +static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *); +static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *); /* - * Last function called by postgres backend + * Currently active plpython function */ -static PLyProcedure *PLy_last_procedure = NULL; +static PLyProcedure *PLy_curr_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 +/* + * When a callback from Python into PG incurs an error, we temporarily store + * the error information here, and return NULL to the Python interpreter. + * Any further callback attempts immediately fail, and when the Python + * interpreter returns to the calling function, we re-throw the error (even if + * Python thinks it trapped the error and doesn't return NULL). Eventually + * this ought to be improved to let Python code really truly trap the error, + * but that's more of a change from the pre-8.0 semantics than I have time for + * now --- it will only be possible if the callback query is executed inside a + * subtransaction. */ -static volatile int PLy_restart_in_progress = 0; +static ErrorData *PLy_error_in_progress = NULL; static PyObject *PLy_interp_globals = NULL; static PyObject *PLy_interp_safe_globals = NULL; static PyObject *PLy_procedure_cache = NULL; -/* Python exceptions - */ +/* 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 - */ +/* some globals for the python module */ static char PLy_plan_doc[] = { "Store a PostgreSQL plan" }; @@ -291,13 +307,6 @@ static char PLy_result_doc[] = { }; -#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 */ @@ -322,62 +331,51 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo) Datum plpython_call_handler(PG_FUNCTION_ARGS) { - DECLARE_EXC(); Datum retval; - volatile bool is_trigger; + PLyProcedure *save_curr_proc; PLyProcedure *volatile proc = NULL; - enter(); - - if (PLy_first_call) - PLy_init_all(); - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "could not connect to SPI manager"); + elog(ERROR, "SPI_connect failed"); - CALL_LEVEL_INC(); - is_trigger = CALLED_AS_TRIGGER(fcinfo); + save_curr_proc = PLy_curr_procedure; - SAVE_EXC(); - if (TRAP_EXC()) + PG_TRY(); { - RESTORE_EXC(); - CALL_LEVEL_DEC(); - if (PLy_call_level == 0) + if (CALLED_AS_TRIGGER(fcinfo)) { - PLy_restart_in_progress = 0; - PyErr_Clear(); + TriggerData *tdata = (TriggerData *) fcinfo->context; + HeapTuple trv; + + proc = PLy_procedure_get(fcinfo, + RelationGetRelid(tdata->tg_relation)); + PLy_curr_procedure = proc; + trv = PLy_trigger_handler(fcinfo, proc); + retval = PointerGetDatum(trv); } else - PLy_restart_in_progress += 1; - if (proc) { - Py_DECREF(proc->me); + proc = PLy_procedure_get(fcinfo, InvalidOid); + PLy_curr_procedure = proc; + retval = PLy_function_handler(fcinfo, proc); } - RERAISE_EXC(); } - - /* - * elog(DEBUG3, "PLy_restart_in_progress is %d", - * PLy_restart_in_progress); - */ - - proc = PLy_procedure_get(fcinfo, is_trigger); - - if (is_trigger) + PG_CATCH(); { - HeapTuple trv = PLy_trigger_handler(fcinfo, proc); - - retval = PointerGetDatum(trv); + PLy_curr_procedure = save_curr_proc; + if (proc) + { + /* note: Py_DECREF needs braces around it, as of 2003/08 */ + Py_DECREF(proc->me); + } + PyErr_Clear(); + PG_RE_THROW(); } - else - retval = PLy_function_handler(fcinfo, proc); + PG_END_TRY(); - CALL_LEVEL_DEC(); - RESTORE_EXC(); + PLy_curr_procedure = save_curr_proc; Py_DECREF(proc->me); - refc(proc->me); return retval; } @@ -392,89 +390,86 @@ plpython_call_handler(PG_FUNCTION_ARGS) * BEFORE the event and is ROW level. postgres expects the function * to take no arguments and return an argument of type trigger. */ -HeapTuple +static 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()) + PG_TRY(); { - RESTORE_EXC(); + plargs = PLy_trigger_build_args(fcinfo, proc, &rv); + plrv = PLy_procedure_call(proc, "TD", plargs); - Py_XDECREF(plargs); - Py_XDECREF(plrv); + Assert(plrv != NULL); + Assert(!PLy_error_in_progress); - 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"); + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); - srv = PyString_AsString(plrv); - if (strcasecmp(srv, "SKIP") == 0) - rv = NULL; - else if (strcasecmp(srv, "MODIFY") == 0) + /* + * return of None means we're happy with the tuple + */ + if (plrv != Py_None) { - TriggerData *tdata = (TriggerData *) fcinfo->context; + char *srv; + + if (!PyString_Check(plrv)) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None or a string."))); + + srv = PyString_AsString(plrv); + if (pg_strcasecmp(srv, "SKIP") == 0) + rv = NULL; + else if (pg_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\""); + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || + TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + rv = PLy_modify_tuple(proc, plargs, tdata, rv); + else + ereport(WARNING, + (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); + } + else if (pg_strcasecmp(srv, "OK") != 0) + { + /* + * accept "OK" as an alternative to None; otherwise, raise an + * error + */ + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); + } } } + PG_CATCH(); + { + Py_XDECREF(plargs); + Py_XDECREF(plrv); + + PG_RE_THROW(); + } + PG_END_TRY(); Py_DECREF(plargs); Py_DECREF(plrv); - RESTORE_EXC(); - return rv; } -HeapTuple +static HeapTuple PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, HeapTuple otup) { - DECLARE_EXC(); PyObject *volatile plntup; PyObject *volatile plkeys; PyObject *volatile platt; @@ -483,7 +478,6 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, HeapTuple rtup; int natts, i, - j, attn, atti; int *volatile modattrs; @@ -496,337 +490,471 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, modvalues = NULL; modnulls = NULL; - enter(); - - SAVE_EXC(); - if (TRAP_EXC()) + PG_TRY(); { - 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); - - if (natts != proc->result.out.r.natts) - elog(ERROR, "TD[\"new\"] has an incorrect number of keys"); + if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) + ereport(ERROR, + (errmsg("TD[\"new\"] deleted, cannot modify row"))); + if (!PyDict_Check(plntup)) + ereport(ERROR, + (errmsg("TD[\"new\"] is not a dictionary"))); + Py_INCREF(plntup); - modattrs = palloc(natts * sizeof(int)); - modvalues = palloc(natts * sizeof(Datum)); - for (i = 0; i < natts; i++) - { - modattrs[i] = i + 1; - modvalues[i] = (Datum) NULL; - } - modnulls = palloc(natts + 1); - memset(modnulls, 'n', natts); - modnulls[natts] = '\0'; + plkeys = PyDict_Keys(plntup); + natts = PyList_Size(plkeys); - tupdesc = tdata->tg_relation->rd_att; + modattrs = (int *) palloc(natts * sizeof(int)); + modvalues = (Datum *) palloc(natts * sizeof(Datum)); + modnulls = (char *) palloc(natts * sizeof(char)); - for (j = 0; j < natts; j++) - { - char *src; + tupdesc = tdata->tg_relation->rd_att; - platt = PyList_GetItem(plkeys, j); - if (!PyString_Check(platt)) - elog(ERROR, "attribute is not a string"); - attn = modattrs[j] = SPI_fnumber(tupdesc, PyString_AsString(platt)); + for (i = 0; i < natts; i++) + { + char *src; - if (attn == SPI_ERROR_NOATTRIBUTE) - elog(ERROR, "invalid attribute \"%s\" in tuple", - PyString_AsString(platt)); - atti = attn - 1; + platt = PyList_GetItem(plkeys, i); + if (!PyString_Check(platt)) + ereport(ERROR, + (errmsg("name of TD[\"new\"] attribute at ordinal position %d is not a string", i))); + attn = SPI_fnumber(tupdesc, PyString_AsString(platt)); + if (attn == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", + PyString_AsString(platt)))); + atti = attn - 1; - plval = PyDict_GetItem(plntup, platt); - if (plval == NULL) - elog(FATAL, "python interpreter is probably corrupted"); + plval = PyDict_GetItem(plntup, platt); + if (plval == NULL) + elog(FATAL, "Python interpreter is probably corrupted"); - Py_INCREF(plval); + Py_INCREF(plval); - if (plval != Py_None) - { - plstr = PyObject_Str(plval); - src = PyString_AsString(plstr); + modattrs[i] = attn; - modvalues[j] = FunctionCall3(&proc->result.out.r.atts[atti].typfunc, - CStringGetDatum(src), - ObjectIdGetDatum(proc->result.out.r.atts[atti].typelem), - Int32GetDatum(tupdesc->attrs[atti]->atttypmod)); - modnulls[j] = ' '; + if (tupdesc->attrs[atti]->attisdropped) + { + modvalues[i] = (Datum) 0; + modnulls[i] = 'n'; + } + else if (plval != Py_None) + { + plstr = PyObject_Str(plval); + if (!plstr) + PLy_elog(ERROR, "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row", + proc->proname); + src = PyString_AsString(plstr); + + modvalues[i] = + InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, + src, + proc->result.out.r.atts[atti].typioparam, + tupdesc->attrs[atti]->atttypmod); + modnulls[i] = ' '; + + Py_DECREF(plstr); + plstr = NULL; + } + else + { + modvalues[i] = + InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, + NULL, + proc->result.out.r.atts[atti].typioparam, + tupdesc->attrs[atti]->atttypmod); + modnulls[i] = 'n'; + } - Py_DECREF(plstr); - plstr = NULL; + Py_DECREF(plval); + plval = NULL; } - Py_DECREF(plval); - plval = NULL; + rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, + modattrs, modvalues, modnulls); + if (rtup == NULL) + elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result); } - rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs, - modvalues, modnulls); + PG_CATCH(); + { + Py_XDECREF(plntup); + Py_XDECREF(plkeys); + Py_XDECREF(plval); + Py_XDECREF(plstr); - /* - * FIXME -- these leak if not explicitly pfree'd by other elog - * calls, no? - */ - pfree(modattrs); - pfree(modvalues); - pfree(modnulls); + if (modnulls) + pfree(modnulls); + if (modvalues) + pfree(modvalues); + if (modattrs) + pfree(modattrs); - if (rtup == NULL) - elog(ERROR, "SPI_modifytuple failed -- error %d", SPI_result); + PG_RE_THROW(); + } + PG_END_TRY(); Py_DECREF(plntup); Py_DECREF(plkeys); - RESTORE_EXC(); + pfree(modattrs); + pfree(modvalues); + pfree(modnulls); return rtup; } -PyObject * +static PyObject * PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple *rv) { - DECLARE_EXC(); - TriggerData *tdata; + TriggerData *tdata = (TriggerData *) fcinfo->context; PyObject *pltname, *pltevent, *pltwhen, *pltlevel, - *pltrelid; + *pltrelid, + *plttablename, + *plttableschema; PyObject *pltargs, *pytnew, *pytold; PyObject *volatile pltdata = NULL; char *stroid; - enter(); - - SAVE_EXC(); - if (TRAP_EXC()) + PG_TRY(); { - 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); - + pltdata = PyDict_New(); + if (!pltdata) + PLy_elog(ERROR, "could not create new dictionary while building trigger arguments"); + + 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); + + stroid = SPI_getrelname(tdata->tg_relation); + plttablename = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "table_name", plttablename); + Py_DECREF(plttablename); + pfree(stroid); + + stroid = SPI_getnspname(tdata->tg_relation); + plttableschema = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "table_schema", plttableschema); + Py_DECREF(plttableschema); + 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_BEFORE(tdata->tg_event)) - pltwhen = PyString_FromString("BEFORE"); - else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) - pltwhen = PyString_FromString("AFTER"); - else - pltwhen = PyString_FromString("UNKNOWN"); - PyDict_SetItemString(pltdata, "when", pltwhen); - Py_DECREF(pltwhen); - - if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) - pltlevel = PyString_FromString("ROW"); - else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) - pltlevel = PyString_FromString("STATEMENT"); - else - pltlevel = PyString_FromString("UNKNOWN"); - 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 */ + } - 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 - { - pltevent = PyString_FromString("UNKNOWN"); - PyDict_SetItemString(pltdata, "old", Py_None); - PyDict_SetItemString(pltdata, "new", Py_None); - *rv = tdata->tg_trigtuple; - } - PyDict_SetItemString(pltdata, "event", pltevent); - Py_DECREF(pltevent); + 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 if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) + pltevent = PyString_FromString("TRUNCATE"); + else + { + elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); + pltevent = NULL; /* keep compiler quiet */ + } - if (tdata->tg_trigger->tgnargs) - { - /* - * all strings... - */ - int i; - PyObject *pltarg; + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + } + else + elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); - pltargs = PyList_New(tdata->tg_trigger->tgnargs); - for (i = 0; i < tdata->tg_trigger->tgnargs; i++) + if (tdata->tg_trigger->tgnargs) { - pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); - /* - * stolen, don't Py_DECREF + * all strings... */ - PyList_SetItem(pltargs, i, pltarg); + 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); } - else + PG_CATCH(); { - Py_INCREF(Py_None); - pltargs = Py_None; + Py_XDECREF(pltdata); + PG_RE_THROW(); } - PyDict_SetItemString(pltdata, "args", pltargs); - Py_DECREF(pltargs); - - RESTORE_EXC(); + PG_END_TRY(); return pltdata; } -/* function handler and friends - */ -Datum +/* function handler and friends */ +static 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()) + PG_TRY(); { - RESTORE_EXC(); + if (!proc->is_setof || proc->setof == NULL) + { + /* Simple type returning function or first time for SETOF function */ + plargs = PLy_function_build_args(fcinfo, proc); + plrv = PLy_procedure_call(proc, "args", plargs); + if (!proc->is_setof) + + /* + * SETOF function parameters will be deleted when last row is + * returned + */ + PLy_function_delete_args(proc); + Assert(plrv != NULL); + Assert(!PLy_error_in_progress); + } - Py_XDECREF(plargs); - Py_XDECREF(plrv); - Py_XDECREF(plrv_so); + /* + * 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"); - RERAISE_EXC(); - } + if (proc->is_setof) + { + bool has_error = false; + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; - plargs = PLy_function_build_args(fcinfo, proc); - plrv = PLy_procedure_call(proc, "args", plargs); + if (proc->setof == NULL) + { + /* first time -- do checks and setup */ + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_ValuePerCall) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported set function return mode"), + errdetail("PL/Python set-returning functions only support returning only value per call."))); + } + rsi->returnMode = SFRM_ValuePerCall; + + /* Make iterator out of returned object */ + proc->setof = PyObject_GetIter(plrv); + Py_DECREF(plrv); + plrv = NULL; + + if (proc->setof == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("returned object cannot be iterated"), + errdetail("PL/Python set-returning functions must return an iterable object."))); + } - /* - * 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"); + /* Fetch next from iterator */ + plrv = PyIter_Next(proc->setof); + if (plrv) + rsi->isDone = ExprMultipleResult; + else + { + rsi->isDone = ExprEndResult; + has_error = PyErr_Occurred() != NULL; + } - 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); + if (rsi->isDone == ExprEndResult) + { + /* Iterator is exhausted or error happened */ + Py_DECREF(proc->setof); + proc->setof = NULL; + + Py_XDECREF(plargs); + Py_XDECREF(plrv); + Py_XDECREF(plrv_so); + + PLy_function_delete_args(proc); + + if (has_error) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("error fetching next item from iterator"))); + + fcinfo->isnull = true; + return (Datum) NULL; + } + } /* - * FIXME is this dead code? i'm pretty sure it is for unnested - * calls, but not for nested calls + * If the function is declared to return void, the Python return value + * must be None. For void-returning functions, we also treat a None + * return value as a special "void datum" rather than NULL (as is the + * case for non-void-returning functions). */ - RAISE_EXC(1); -#endif - } + if (proc->result.out.d.typoid == VOIDOID) + { + if (plrv != Py_None) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function with return type \"void\" did not return None"))); - /* - * 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 - */ + fcinfo->isnull = false; + rv = (Datum) 0; + } + else if (plrv == Py_None) + { + fcinfo->isnull = true; + if (proc->result.is_rowtype < 1) + rv = InputFunctionCall(&proc->result.out.d.typfunc, + NULL, + proc->result.out.d.typioparam, + -1); + else + /* Tuple as None */ + rv = (Datum) NULL; + } + else if (proc->result.is_rowtype >= 1) + { + HeapTuple tuple = NULL; + + if (PySequence_Check(plrv)) + /* composite type as sequence (tuple, list etc) */ + tuple = PLySequence_ToTuple(&proc->result, plrv); + else if (PyMapping_Check(plrv)) + /* composite type as mapping (currently only dict) */ + tuple = PLyMapping_ToTuple(&proc->result, plrv); + else + /* returned as smth, must provide method __getattr__(name) */ + tuple = PLyObject_ToTuple(&proc->result, plrv); - if (plrv == Py_None) - { - fcinfo->isnull = true; - rv = (Datum) NULL; + if (tuple != NULL) + { + fcinfo->isnull = false; + rv = HeapTupleGetDatum(tuple); + } + else + { + fcinfo->isnull = true; + rv = (Datum) NULL; + } + } + else + { + fcinfo->isnull = false; + plrv_so = PyObject_Str(plrv); + if (!plrv_so) + PLy_elog(ERROR, "could not create string representation of Python object in PL/Python function \"%s\" while creating return value", proc->proname); + plrv_sc = PyString_AsString(plrv_so); + rv = InputFunctionCall(&proc->result.out.d.typfunc, + plrv_sc, + proc->result.out.d.typioparam, + -1); + } } - else + PG_CATCH(); { - 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)); - } + Py_XDECREF(plargs); + Py_XDECREF(plrv); + Py_XDECREF(plrv_so); - RESTORE_EXC(); + PG_RE_THROW(); + } + PG_END_TRY(); Py_XDECREF(plargs); Py_DECREF(plrv); @@ -835,107 +963,146 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) return rv; } -PyObject * +static 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; + rv = PyEval_EvalCode((PyCodeObject *) proc->code, + proc->globals, proc->globals); + + /* + * If there was an error in a PG callback, propagate that no matter what + * Python claims about its success. + */ + if (PLy_error_in_progress) + { + ErrorData *edata = PLy_error_in_progress; + + PLy_error_in_progress = NULL; + ReThrowError(edata); + } - if ((rv == NULL) || (PyErr_Occurred())) + if (rv == NULL || PyErr_Occurred()) { Py_XDECREF(rv); - if (!PLy_restart_in_progress) - PLy_elog(ERROR, "function \"%s\" failed", proc->proname); - RAISE_EXC(1); + PLy_elog(ERROR, "PL/Python function \"%s\" failed", proc->proname); } return rv; } -PyObject * +static 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++) + PG_TRY(); { - if (proc->args[i].is_rel == 1) - { - TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i]; - - arg = PLyDict_FromTuple(&(proc->args[i]), slot->val, - slot->ttc_tupleDescriptor); - } - else + args = PyList_New(proc->nargs); + for (i = 0; i < proc->nargs; i++) { - if (!fcinfo->argnull[i]) + if (proc->args[i].is_rowtype > 0) { - 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 (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); + ReleaseTupleDesc(tupdesc); + } } else - arg = NULL; - } + { + if (fcinfo->argnull[i]) + arg = NULL; + else + { + char *ct; - if (arg == NULL) - { - Py_INCREF(Py_None); - arg = Py_None; - } + ct = OutputFunctionCall(&(proc->args[i].in.d.typfunc), + fcinfo->arg[i]); + arg = (proc->args[i].in.d.func) (ct); + pfree(ct); + } + } - /* - * FIXME -- error check this - */ - PyList_SetItem(args, i, arg); + if (arg == NULL) + { + Py_INCREF(Py_None); + arg = Py_None; + } + + if (PyList_SetItem(args, i, arg) == -1) + PLy_elog(ERROR, "PyList_SetItem() failed for PL/Python function \"%s\" while setting up arguments", proc->proname); + + if (proc->argnames && proc->argnames[i] && + PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) + PLy_elog(ERROR, "PyDict_SetItemString() failed for PL/Python function \"%s\" while setting up arguments", proc->proname); + arg = NULL; + } } + PG_CATCH(); + { + Py_XDECREF(arg); + Py_XDECREF(args); - RESTORE_EXC(); + PG_RE_THROW(); + } + PG_END_TRY(); return args; } -/* PLyProcedure functions +static void +PLy_function_delete_args(PLyProcedure * proc) +{ + int i; + + if (!proc->argnames) + return; + + for (i = 0; i < proc->nargs; i++) + if (proc->argnames[i]) + PyDict_DelItemString(proc->globals, proc->argnames[i]); +} + + +/* + * 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, bool is_trigger) +PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) { Oid fn_oid; HeapTuple procTup; @@ -944,8 +1111,6 @@ PLy_procedure_get(FunctionCallInfo fcinfo, bool is_trigger) PLyProcedure *proc = NULL; int rv; - enter(); - fn_oid = fcinfo->flinfo->fn_oid; procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), @@ -953,10 +1118,8 @@ PLy_procedure_get(FunctionCallInfo fcinfo, bool is_trigger) if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); - rv = snprintf(key, sizeof(key), "%u%s", - fn_oid, - is_trigger ? "_trigger" : ""); - if ((rv >= sizeof(key)) || (rv < 0)) + 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); @@ -967,14 +1130,12 @@ PLy_procedure_get(FunctionCallInfo fcinfo, bool is_trigger) 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)) + !ItemPointerEquals(&proc->fn_tid, &procTup->t_self)) { Py_DECREF(plproc); proc = NULL; @@ -982,7 +1143,24 @@ PLy_procedure_get(FunctionCallInfo fcinfo, bool is_trigger) } if (proc == NULL) - proc = PLy_procedure_create(fcinfo, is_trigger, procTup, key); + proc = PLy_procedure_create(procTup, tgreloid, key); + + if (OidIsValid(tgreloid)) + { + /* + * Input/output conversion for trigger tuples. Use the result + * TypeInfo variable to store the tuple conversion info. We + * do this over again on each call to cover the possibility that + * the relation's tupdesc changed since the trigger was last called. + * PLy_input_tuple_funcs and PLy_output_tuple_funcs are responsible + * for not doing repetitive work. + */ + TriggerData *tdata = (TriggerData *) fcinfo->context; + + Assert(CALLED_AS_TRIGGER(fcinfo)); + PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); + PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); + } ReleaseSysCache(procTup); @@ -990,159 +1168,223 @@ PLy_procedure_get(FunctionCallInfo fcinfo, bool is_trigger) } static PLyProcedure * -PLy_procedure_create(FunctionCallInfo fcinfo, bool is_trigger, - HeapTuple procTup, char *key) +PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key) { - char procName[NAMEDATALEN+256]; - - DECLARE_EXC(); + char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; char *volatile procSource = NULL; - Datum procDatum; + Datum prosrcdatum; + bool isnull; int i, rv; - enter(); - procStruct = (Form_pg_proc) GETSTRUCT(procTup); - rv = snprintf(procName, sizeof(procName), - "__plpython_procedure_%s_%u%s", - NameStr(procStruct->proname), - fcinfo->flinfo->fn_oid, - is_trigger ? "_trigger" : ""); - if ((rv >= sizeof(procName)) || (rv < 0)) + if (OidIsValid(tgreloid)) + rv = snprintf(procName, sizeof(procName), + "__plpython_procedure_%s_%u_trigger_%u", + NameStr(procStruct->proname), + HeapTupleGetOid(procTup), + tgreloid); + else + rv = snprintf(procName, sizeof(procName), + "__plpython_procedure_%s_%u", + NameStr(procStruct->proname), + HeapTupleGetOid(procTup)); + 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->proname = PLy_strdup(NameStr(procStruct->proname)); + proc->pyname = PLy_strdup(procName); proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); - proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); + proc->fn_tid = procTup->t_self; + /* Remember if function is STABLE/IMMUTABLE */ + proc->fn_readonly = + (procStruct->provolatile != PROVOLATILE_VOLATILE); 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; + proc->is_setof = procStruct->proretset; + proc->setof = NULL; + proc->argnames = 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 (!is_trigger) + PG_TRY(); { - HeapTuple rvTypeTup; - Form_pg_type rvTypeStruct; + /* + * get information required for output conversion of the return value, + * but only if this isn't a trigger. + */ + if (!OidIsValid(tgreloid)) + { + 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); + + /* Disallow pseudotype result, except for void */ + if (rvTypeStruct->typtype == TYPTYPE_PSEUDO && + procStruct->prorettype != VOIDOID) + { + if (procStruct->prorettype == TRIGGEROID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("trigger functions can only be called as triggers"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/Python functions cannot return type %s", + format_type_be(procStruct->prorettype)))); + } - rvTypeTup = SearchSysCache(TYPEOID, - ObjectIdGetDatum(procStruct->prorettype), - 0, 0, 0); - if (!HeapTupleIsValid(rvTypeTup)) - elog(ERROR, "cache lookup failed for type %u", - procStruct->prorettype); + if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE) + { + /* + * Tuple: set up later, during first call to + * PLy_function_handler + */ + proc->result.out.d.typoid = procStruct->prorettype; + proc->result.is_rowtype = 2; + } + else + PLy_output_datum_func(&proc->result, rvTypeTup); - rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); - if (rvTypeStruct->typrelid == InvalidOid) - 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); + } - ReleaseSysCache(rvTypeTup); - } - else - { /* - * input/output conversion for trigger tuples. use the result - * TypeInfo variable to store the tuple conversion info. + * Now get information required for input conversion of the + * procedure's arguments. Note that we ignore output arguments + * here --- since we don't support returning record, and that was + * already checked above, there's no need to worry about multiple + * output arguments. */ - TriggerData *tdata = (TriggerData *) fcinfo->context; + if (procStruct->pronargs) + { + Oid *types; + char **names, + *modes; + int i, + pos, + total; + + /* extract argument type info from the pg_proc tuple */ + total = get_func_arg_info(procTup, &types, &names, &modes); + + /* count number of in+inout args into proc->nargs */ + if (modes == NULL) + proc->nargs = total; + else + { + /* proc->nargs was initialized to 0 above */ + for (i = 0; i < total; i++) + { + if (modes[i] != PROARGMODE_OUT && + modes[i] != PROARGMODE_TABLE) + (proc->nargs)++; + } + } - PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - } + proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs); + for (i = pos = 0; i < total; i++) + { + HeapTuple argTypeTup; + Form_pg_type argTypeStruct; - /* - * 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; + if (modes && + (modes[i] == PROARGMODE_OUT || + modes[i] == PROARGMODE_TABLE)) + continue; /* skip OUT arguments */ - 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); + Assert(types[i] == procStruct->proargtypes.values[pos]); - if (argTypeStruct->typrelid == InvalidOid) - PLy_input_datum_func(&(proc->args[i]), - procStruct->proargtypes[i], - argTypeStruct); - else - { - TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i]; + argTypeTup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(types[i]), + 0, 0, 0); + if (!HeapTupleIsValid(argTypeTup)) + elog(ERROR, "cache lookup failed for type %u", types[i]); + argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); - PLy_input_tuple_funcs(&(proc->args[i]), - slot->ttc_tupleDescriptor); - } + /* check argument type is OK, set up I/O function info */ + switch (argTypeStruct->typtype) + { + case TYPTYPE_PSEUDO: + /* Disallow pseudotype argument */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/Python functions cannot accept type %s", + format_type_be(types[i])))); + break; + case TYPTYPE_COMPOSITE: + /* we'll set IO funcs at first call */ + proc->args[pos].is_rowtype = 2; + break; + default: + PLy_input_datum_func(&(proc->args[pos]), + types[i], + argTypeTup); + break; + } - ReleaseSysCache(argTypeTup); - } + /* get argument name */ + proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL; + ReleaseSysCache(argTypeTup); - /* - * get the text of the function. - */ - procDatum = DirectFunctionCall1(textout, - PointerGetDatum(&procStruct->prosrc)); - procSource = DatumGetCString(procDatum); + pos++; + } + } + + /* + * get the text of the function. + */ + prosrcdatum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc"); + procSource = TextDatumGetCString(prosrcdatum); - PLy_procedure_compile(proc, procSource); + PLy_procedure_compile(proc, procSource); - pfree(procSource); + pfree(procSource); - proc->me = PyCObject_FromVoidPtr(proc, NULL); - PyDict_SetItemString(PLy_procedure_cache, key, proc->me); + proc->me = PyCObject_FromVoidPtr(proc, NULL); + PyDict_SetItemString(PLy_procedure_cache, key, proc->me); + } + PG_CATCH(); + { + PLy_procedure_delete(proc); + if (procSource) + pfree(procSource); - RESTORE_EXC(); + PG_RE_THROW(); + } + PG_END_TRY(); return proc; } -void +static 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 + * 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); @@ -1154,10 +1396,10 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src) crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); free(msrc); - if ((crv != NULL) && (!PyErr_Occurred())) + if (crv != NULL && (!PyErr_Occurred())) { int clen; - char call[NAMEDATALEN+256]; + char call[NAMEDATALEN + 256]; Py_DECREF(crv); @@ -1165,19 +1407,19 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src) * compile a call to the function */ clen = snprintf(call, sizeof(call), "%s()", proc->pyname); - if ((clen < 0) || (clen >= sizeof(call))) + if (clen < 0 || clen >= sizeof(call)) elog(ERROR, "string would overflow buffer"); proc->code = Py_CompileString(call, "", Py_eval_input); - if ((proc->code != NULL) && (!PyErr_Occurred())) + if (proc->code != NULL && (!PyErr_Occurred())) return; } else Py_XDECREF(crv); - PLy_elog(ERROR, "could not compile function \"%s\"", proc->proname); + PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", proc->proname); } -char * +static char * PLy_procedure_munge_source(const char *name, const char *src) { char *mrc, @@ -1186,8 +1428,6 @@ PLy_procedure_munge_source(const char *name, const char *src) size_t mlen, plen; - enter(); - /* * room for function source and the def statement */ @@ -1202,10 +1442,14 @@ PLy_procedure_munge_source(const char *name, const char *src) while (*sp != '\0') { - if (*sp == '\n') + if (*sp == '\r' && *(sp + 1) == '\n') + sp++; + + if (*sp == '\n' || *sp == '\r') { - *mp++ = *sp++; + *mp++ = '\n'; *mp++ = '\t'; + sp++; } else *mp++ = *sp++; @@ -1220,13 +1464,11 @@ PLy_procedure_munge_source(const char *name, const char *src) return mrc; } -void +static void PLy_procedure_delete(PLyProcedure * proc) { int i; - enter(); - Py_XDECREF(proc->code); Py_XDECREF(proc->statics); Py_XDECREF(proc->globals); @@ -1236,38 +1478,51 @@ PLy_procedure_delete(PLyProcedure * proc) if (proc->pyname) PLy_free(proc->pyname); for (i = 0; i < proc->nargs; i++) - if (proc->args[i].is_rel == 1) + { + 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(); + if (proc->argnames && proc->argnames[i]) + PLy_free(proc->argnames[i]); + } + if (proc->argnames) + PLy_free(proc->argnames); } -/* conversion functions. remember output from python is - * input to postgresql, and vis versa. +/* + * Conversion functions. Remember output from Python is input to + * PostgreSQL, and vice versa. */ -void +static void PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) { int i; - enter(); - - if (arg->is_rel == 0) + if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); + arg->is_rowtype = 1; - arg->is_rel = 1; - arg->in.r.natts = desc->natts; - arg->in.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb)); + if (arg->in.r.natts != desc->natts) + { + if (arg->in.r.atts) + PLy_free(arg->in.r.atts); + arg->in.r.natts = desc->natts; + arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); + } for (i = 0; i < desc->natts; i++) { HeapTuple typeTup; - Form_pg_type typeStruct; + + if (desc->attrs[i]->attisdropped) + continue; + + if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid) + continue; /* already set up this entry */ typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(desc->attrs[i]->atttypid), @@ -1275,34 +1530,41 @@ PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) 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); + typeTup); ReleaseSysCache(typeTup); } } -void +static void PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) { int i; - enter(); - - if (arg->is_rel == 0) + if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); + arg->is_rowtype = 1; - arg->is_rel = 1; - arg->out.r.natts = desc->natts; - arg->out.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb)); + if (arg->out.r.natts != desc->natts) + { + if (arg->out.r.atts) + PLy_free(arg->out.r.atts); + arg->out.r.natts = desc->natts; + arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); + } for (i = 0; i < desc->natts; i++) { HeapTuple typeTup; - Form_pg_type typeStruct; + + if (desc->attrs[i]->attisdropped) + continue; + + if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid) + continue; /* already set up this entry */ typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(desc->attrs[i]->atttypid), @@ -1310,52 +1572,51 @@ PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) 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); + PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); ReleaseSysCache(typeTup); } } -void -PLy_output_datum_func(PLyTypeInfo * arg, Form_pg_type typeStruct) +static void +PLy_output_datum_func(PLyTypeInfo * arg, HeapTuple typeTup) { - enter(); - - if (arg->is_rel == 1) + if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); - arg->is_rel = 0; - PLy_output_datum_func2(&(arg->out.d), typeStruct); + arg->is_rowtype = 0; + PLy_output_datum_func2(&(arg->out.d), typeTup); } -void -PLy_output_datum_func2(PLyObToDatum * arg, Form_pg_type typeStruct) +static void +PLy_output_datum_func2(PLyObToDatum * arg, HeapTuple typeTup) { - enter(); + Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); perm_fmgr_info(typeStruct->typinput, &arg->typfunc); - arg->typelem = typeStruct->typelem; + arg->typoid = HeapTupleGetOid(typeTup); + arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; } -void -PLy_input_datum_func(PLyTypeInfo * arg, Oid typeOid, Form_pg_type typeStruct) +static void +PLy_input_datum_func(PLyTypeInfo * arg, Oid typeOid, HeapTuple typeTup) { - enter(); - - if (arg->is_rel == 1) + if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); - arg->is_rel = 0; - PLy_input_datum_func2(&(arg->in.d), typeOid, typeStruct); + arg->is_rowtype = 0; + PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); } -void -PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, Form_pg_type typeStruct) +static void +PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, HeapTuple typeTup) { + Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); + /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); - arg->typelem = typeStruct->typelem; + arg->typoid = HeapTupleGetOid(typeTup); + arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; /* Determine which kind of Python object we will convert to */ @@ -1382,19 +1643,19 @@ PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, Form_pg_type typeStruct) } } -void +static void PLy_typeinfo_init(PLyTypeInfo * arg) { - arg->is_rel = -1; + arg->is_rowtype = -1; arg->in.r.natts = arg->out.r.natts = 0; arg->in.r.atts = NULL; arg->out.r.atts = NULL; } -void +static void PLy_typeinfo_dealloc(PLyTypeInfo * arg) { - if (arg->is_rel == 1) + if (arg->is_rowtype == 1) { if (arg->in.r.atts) PLy_free(arg->in.r.atts); @@ -1403,127 +1664,359 @@ PLy_typeinfo_dealloc(PLyTypeInfo * arg) } } -/* assumes that a bool is always returned as a 't' or 'f' - */ -PyObject * +/* assumes that a bool is always returned as a 't' or 'f' */ +static PyObject * PLyBool_FromString(const char *src) { - enter(); - + /* + * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for + * generating SQL from trigger functions, but those are only supported in + * Python >= 2.3, and we support older versions. + * http://docs.python.org/api/boolObjects.html + */ if (src[0] == 't') - return PyInt_FromLong(1); - return PyInt_FromLong(0); + return PyBool_FromLong(1); + return PyBool_FromLong(0); } -PyObject * +static PyObject * PLyFloat_FromString(const char *src) { double v; char *eptr; - enter(); - errno = 0; v = strtod(src, &eptr); - if ((*eptr != '\0') || (errno)) + if (*eptr != '\0' || errno) return NULL; return PyFloat_FromDouble(v); } -PyObject * +static PyObject * PLyInt_FromString(const char *src) { long v; char *eptr; - enter(); - errno = 0; v = strtol(src, &eptr, 0); - if ((*eptr != '\0') || (errno)) + if (*eptr != '\0' || errno) return NULL; return PyInt_FromLong(v); } -PyObject * +static PyObject * PLyLong_FromString(const char *src) { return PyLong_FromString((char *) src, NULL, 0); } -PyObject * +static PyObject * PLyString_FromString(const char *src) { return PyString_FromString(src); } -PyObject * +static PyObject * PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) { - DECLARE_EXC(); PyObject *volatile dict; int i; - enter(); - - if (info->is_rel != 1) + 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"); + PLy_elog(ERROR, "could not create new dictionary"); - SAVE_EXC(); - if (TRAP_EXC()) + PG_TRY(); { - RESTORE_EXC(); - Py_DECREF(dict); + for (i = 0; i < info->in.r.natts; i++) + { + char *key, + *vsrc; + Datum vattr; + bool is_null; + PyObject *value; + + if (desc->attrs[i]->attisdropped) + continue; - RERAISE_EXC(); + 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 + { + vsrc = OutputFunctionCall(&info->in.r.atts[i].typfunc, + vattr); + + /* + * no exceptions allowed + */ + value = info->in.r.atts[i].func(vsrc); + pfree(vsrc); + PyDict_SetItemString(dict, key, value); + Py_DECREF(value); + } + } + } + PG_CATCH(); + { + Py_DECREF(dict); + PG_RE_THROW(); } + PG_END_TRY(); + + return dict; +} + + +static HeapTuple +PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) +{ + TupleDesc desc; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + Assert(PyMapping_Check(mapping)); - for (i = 0; i < info->in.r.natts; i++) + desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1); + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) { - char *key, - *vsrc; - Datum vattr, - vdat; - bool is_null; - PyObject *value; + char *key; + PyObject *volatile value, + *volatile so; key = NameStr(desc->attrs[i]->attname); - vattr = heap_getattr(tuple, (i + 1), desc, &is_null); + value = so = NULL; + PG_TRY(); + { + value = PyMapping_GetItemString(mapping, key); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + char *valuestr; + + so = PyObject_Str(value); + if (so == NULL) + PLy_elog(ERROR, "could not compute string representation of Python object"); + valuestr = PyString_AsString(so); + + values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc + ,valuestr + ,info->out.r.atts[i].typioparam + ,-1); + Py_DECREF(so); + so = NULL; + nulls[i] = false; + } + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("key \"%s\" not found in mapping", key), + errhint("To return null in a column, " + "add the value None to the mapping with the key named after the column."))); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(so); + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } - if ((is_null) || (info->in.r.atts[i].func == NULL)) - PyDict_SetItemString(dict, key, Py_None); - else + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; +} + + +static HeapTuple +PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) +{ + TupleDesc desc; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + Assert(PySequence_Check(sequence)); + + /* + * Check that sequence length is exactly same as PG tuple's. We actually + * can ignore exceeding items or assume missing ones as null but to avoid + * plpython developer's errors we are strict here + */ + desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1); + if (PySequence_Length(sequence) != desc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("length of returned sequence did not match number of columns in row"))); + + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + PyObject *volatile value, + *volatile so; + + value = so = NULL; + PG_TRY(); + { + value = PySequence_GetItem(sequence, i); + Assert(value); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + char *valuestr; + + so = PyObject_Str(value); + if (so == NULL) + PLy_elog(ERROR, "could not compute string representation of Python object"); + valuestr = PyString_AsString(so); + values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc + ,valuestr + ,info->out.r.atts[i].typioparam + ,-1); + Py_DECREF(so); + so = NULL; + nulls[i] = false; + } + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(so); + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; +} + + +static HeapTuple +PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) +{ + TupleDesc desc; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1); + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + char *key; + PyObject *volatile value, + *volatile so; + + key = NameStr(desc->attrs[i]->attname); + value = so = NULL; + PG_TRY(); + { + value = PyObject_GetAttrString(object, key); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + char *valuestr; + + so = PyObject_Str(value); + if (so == NULL) + PLy_elog(ERROR, "could not compute string representation of Python object"); + valuestr = PyString_AsString(so); + values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc + ,valuestr + ,info->out.r.atts[i].typioparam + ,-1); + Py_DECREF(so); + so = NULL; + nulls[i] = false; + } + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" does not exist in Python object", key), + errhint("To return null in a column, " + "let the returned object have an attribute named " + "after column with value None."))); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); { - 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); + Py_XDECREF(so); + Py_XDECREF(value); + PG_RE_THROW(); } + PG_END_TRY(); } - RESTORE_EXC(); + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); - return dict; + return tuple; } -/* initialization, some python variables function declared here - */ -/* interface to postgresql elog - */ +/* 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 *); @@ -1532,8 +2025,7 @@ static PyObject *PLy_warning(PyObject *, PyObject *); static PyObject *PLy_error(PyObject *, PyObject *); static PyObject *PLy_fatal(PyObject *, PyObject *); -/* PLyPlanObject, PLyResultObject and SPI interface - */ +/* 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 *); @@ -1543,25 +2035,19 @@ 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 Py_ssize_t PLy_result_length(PyObject *); +static PyObject *PLy_result_item(PyObject *, Py_ssize_t); +static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t); +static int PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *); +static int PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, 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_query(char *query, long limit); +static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long); static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int); @@ -1575,9 +2061,9 @@ static PyTypeObject PLy_PlanType = { /* * methods */ - (destructor) PLy_plan_dealloc, /* tp_dealloc */ + PLy_plan_dealloc, /* tp_dealloc */ 0, /* tp_print */ - (getattrfunc) PLy_plan_getattr, /* tp_getattr */ + PLy_plan_getattr, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ @@ -1590,24 +2076,23 @@ static PyTypeObject PLy_PlanType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - 0, /* tp_xxx4 */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ PLy_plan_doc, /* tp_doc */ }; static PyMethodDef PLy_plan_methods[] = { - {"status", (PyCFunction) PLy_plan_status, METH_VARARGS, NULL}, + {"status", 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 */ + PLy_result_length, /* sq_length */ + NULL, /* sq_concat */ + NULL, /* sq_repeat */ + PLy_result_item, /* sq_item */ + PLy_result_slice, /* sq_slice */ + PLy_result_ass_item, /* sq_ass_item */ + PLy_result_ass_slice, /* sq_ass_slice */ }; static PyTypeObject PLy_ResultType = { @@ -1620,9 +2105,9 @@ static PyTypeObject PLy_ResultType = { /* * methods */ - (destructor) PLy_result_dealloc, /* tp_dealloc */ + PLy_result_dealloc, /* tp_dealloc */ 0, /* tp_print */ - (getattrfunc) PLy_result_getattr, /* tp_getattr */ + PLy_result_getattr, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ @@ -1635,19 +2120,15 @@ static PyTypeObject PLy_ResultType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - 0, /* tp_xxx4 */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 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}, + {"nrows", PLy_result_nrows, METH_VARARGS, NULL}, + {"status", PLy_result_status, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; -#endif static PyMethodDef PLy_methods[] = { /* @@ -1675,15 +2156,12 @@ static PyMethodDef PLy_methods[] = { }; -/* plan object methods - */ -PyObject * +/* plan object methods */ +static PyObject * PLy_plan_new(void) { PLyPlanObject *ob; - enter(); - if ((ob = PyObject_NEW(PLyPlanObject, &PLy_PlanType)) == NULL) return NULL; @@ -1696,13 +2174,11 @@ PLy_plan_new(void) } -void +static void PLy_plan_dealloc(PyObject * arg) { PLyPlanObject *ob = (PLyPlanObject *) arg; - enter(); - if (ob->plan) SPI_freeplan(ob->plan); if (ob->types) @@ -1716,19 +2192,17 @@ PLy_plan_dealloc(PyObject * arg) PLy_free(ob->args); } - PyMem_DEL(arg); - - leave(); + arg->ob_type->tp_free(arg); } -PyObject * +static PyObject * PLy_plan_getattr(PyObject * self, char *name) { return Py_FindMethod(PLy_plan_methods, self, name); } -PyObject * +static PyObject * PLy_plan_status(PyObject * self, PyObject * args) { if (PyArg_ParseTuple(args, "")) @@ -1737,22 +2211,19 @@ PLy_plan_status(PyObject * self, PyObject * args) return Py_True; /* return PyInt_FromLong(self->status); */ } - PyErr_SetString(PLy_exc_error, "plan.status() takes no arguments"); + PLy_exception_set(PLy_exc_error, "plan.status takes no arguments"); return NULL; } -/* result object methods - */ +/* result object methods */ -PyObject * +static PyObject * PLy_result_new(void) { PLyResultObject *ob; - enter(); - if ((ob = PyObject_NEW(PLyResultObject, &PLy_ResultType)) == NULL) return NULL; @@ -1766,35 +2237,25 @@ PLy_result_new(void) return (PyObject *) ob; } -void +static 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); + arg->ob_type->tp_free(arg); } -PyObject * -PLy_result_getattr(PyObject * self, char *attr) +static PyObject * +PLy_result_getattr(PyObject * self, char *name) { - return NULL; -} - -#ifdef NOT_USED -/* Appear to be unused */ -PyObject * -PLy_result_fetch(PyObject * self, PyObject * args) -{ - return NULL; + return Py_FindMethod(PLy_result_methods, self, name); } -PyObject * +static PyObject * PLy_result_nrows(PyObject * self, PyObject * args) { PLyResultObject *ob = (PLyResultObject *) self; @@ -1803,7 +2264,7 @@ PLy_result_nrows(PyObject * self, PyObject * args) return ob->nrows; } -PyObject * +static PyObject * PLy_result_status(PyObject * self, PyObject * args) { PLyResultObject *ob = (PLyResultObject *) self; @@ -1811,8 +2272,8 @@ PLy_result_status(PyObject * self, PyObject * args) Py_INCREF(ob->status); return ob->status; } -#endif -int + +static Py_ssize_t PLy_result_length(PyObject * arg) { PLyResultObject *ob = (PLyResultObject *) arg; @@ -1820,8 +2281,8 @@ PLy_result_length(PyObject * arg) return PyList_Size(ob->rows); } -PyObject * -PLy_result_item(PyObject * arg, int idx) +static PyObject * +PLy_result_item(PyObject * arg, Py_ssize_t idx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1832,8 +2293,8 @@ PLy_result_item(PyObject * arg, int idx) return rv; } -int -PLy_result_ass_item(PyObject * arg, int idx, PyObject * item) +static int +PLy_result_ass_item(PyObject * arg, Py_ssize_t idx, PyObject * item) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1843,8 +2304,8 @@ PLy_result_ass_item(PyObject * arg, int idx, PyObject * item) return rv; } -PyObject * -PLy_result_slice(PyObject * arg, int lidx, int hidx) +static PyObject * +PLy_result_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1856,8 +2317,8 @@ PLy_result_slice(PyObject * arg, int lidx, int hidx) return rv; } -int -PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice) +static int +PLy_result_ass_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject * slice) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1866,132 +2327,141 @@ PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice) return rv; } -/* SPI interface - */ -PyObject * +/* SPI interface */ +static PyObject * PLy_spi_prepare(PyObject * self, PyObject * args) { - DECLARE_EXC(); PLyPlanObject *plan; PyObject *list = NULL; PyObject *volatile optr = NULL; char *query; void *tmpplan; + MemoryContext oldcontext; - enter(); + /* Can't execute more if we have an unhandled error */ + if (PLy_error_in_progress) + { + PLy_exception_set(PLy_exc_error, "transaction aborted"); + return NULL; + } if (!PyArg_ParseTuple(args, "s|O", &query, &list)) { - PyErr_SetString(PLy_exc_spi_error, - "Invalid arguments for plpy.prepare()"); + PLy_exception_set(PLy_exc_spi_error, + "invalid arguments for plpy.prepare"); return NULL; } - if ((list) && (!PySequence_Check(list))) + if (list && (!PySequence_Check(list))) { - PyErr_SetString(PLy_exc_spi_error, - "Second argument in plpy.prepare() must be a sequence"); + PLy_exception_set(PLy_exc_spi_error, + "second argument of 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) + oldcontext = CurrentMemoryContext; + PG_TRY(); { - int nargs, - i; - - nargs = PySequence_Length(list); - if (nargs > 0) + if (list != NULL) { - plan->nargs = nargs; - plan->types = PLy_malloc(sizeof(Oid) * nargs); - plan->values = PLy_malloc(sizeof(Datum) * nargs); - plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs); + int nargs, + i; - /* - * 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++) + nargs = PySequence_Length(list); + if (nargs > 0) { - char *sptr; - HeapTuple typeTup; - Form_pg_type typeStruct; - - optr = PySequence_GetItem(list, i); - if (!PyString_Check(optr)) + 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++) { - PyErr_SetString(PLy_exc_spi_error, - "Type names must be strings."); - RAISE_EXC(1); + PLy_typeinfo_init(&plan->args[i]); + plan->values[i] = PointerGetDatum(NULL); } - 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->typrelid == InvalidOid) - PLy_output_datum_func(&plan->args[i], typeStruct); - else + + for (i = 0; i < nargs; i++) { - PyErr_SetString(PLy_exc_spi_error, - "tuples not handled in plpy.prepare, yet."); - RAISE_EXC(1); + char *sptr; + HeapTuple typeTup; + Oid typeId; + int32 typmod; + Form_pg_type typeStruct; + + optr = PySequence_GetItem(list, i); + if (!PyString_Check(optr)) + ereport(ERROR, + (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); + sptr = PyString_AsString(optr); + + /******************************************************** + * Resolve argument type names and then look them up by + * oid in the system cache, and remember the required + *information for input conversion. + ********************************************************/ + + parseTypeString(sptr, &typeId, &typmod); + + typeTup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(typeId), + 0, 0, 0); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", typeId); + + Py_DECREF(optr); + optr = NULL; /* this is important */ + + plan->types[i] = typeId; + typeStruct = (Form_pg_type) GETSTRUCT(typeTup); + if (typeStruct->typtype != TYPTYPE_COMPOSITE) + PLy_output_datum_func(&plan->args[i], typeTup); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("plpy.prepare does not support composite types"))); + ReleaseSysCache(typeTup); } - 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); + plan->plan = SPI_prepare(query, plan->nargs, plan->types); + if (plan->plan == NULL) + elog(ERROR, "SPI_prepare failed: %s", + SPI_result_code_string(SPI_result)); + + /* transfer plan from procCxt to topCxt */ + tmpplan = plan->plan; + plan->plan = SPI_saveplan(tmpplan); + SPI_freeplan(tmpplan); + if (plan->plan == NULL) + elog(ERROR, "SPI_saveplan failed: %s", + SPI_result_code_string(SPI_result)); } - - /* transfer plan from procCxt to topCxt */ - tmpplan = plan->plan; - plan->plan = SPI_saveplan(tmpplan); - SPI_freeplan(tmpplan); - if (plan->plan == NULL) + PG_CATCH(); { - PLy_exception_set(PLy_exc_spi_error, - "Unable to save plan. SPI_saveplan failed -- %s.", - PLy_spi_error_string(SPI_result)); - RAISE_EXC(1); + MemoryContextSwitchTo(oldcontext); + PLy_error_in_progress = CopyErrorData(); + FlushErrorState(); + Py_DECREF(plan); + Py_XDECREF(optr); + if (!PyErr_Occurred()) + PLy_exception_set(PLy_exc_spi_error, + "unrecognized error in PLy_spi_prepare"); + /* XXX this oughta be replaced with errcontext mechanism */ + PLy_elog(WARNING, "in PL/Python function \"%s\"", + PLy_procedure_name(PLy_curr_procedure)); + return NULL; } - - RESTORE_EXC(); + PG_END_TRY(); return (PyObject *) plan; } @@ -1999,62 +2469,48 @@ PLy_spi_prepare(PyObject * self, PyObject * args) /* execute(query="select * from foo", limit=5) * execute(plan=plan, values=(foo, bar), limit=5) */ -PyObject * +static PyObject * PLy_spi_execute(PyObject * self, PyObject * args) { char *query; PyObject *plan; PyObject *list = NULL; - int limit = 0; - - enter(); + long limit = 0; -#ifdef NOT_USED - - /* - * there should - hahaha - be an python exception set so just return - * NULL. FIXME -- is this needed? - */ - if (PLy_restart_in_progress) + /* Can't execute more if we have an unhandled error */ + if (PLy_error_in_progress) + { + PLy_exception_set(PLy_exc_error, "transaction aborted"); return NULL; -#endif + } - if (PyArg_ParseTuple(args, "s|i", &query, &limit)) + if (PyArg_ParseTuple(args, "s|l", &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; - } + if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) && + is_PLyPlanObject(plan)) + return PLy_spi_execute_plan(plan, list, limit); - PyErr_SetString(PLy_exc_error, "Expected a query or plan."); + PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan"); return NULL; } -PyObject * -PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) +static PyObject * +PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) { - DECLARE_EXC(); volatile int nargs; int i, rv; PLyPlanObject *plan; - char *nulls; - - enter(); + MemoryContext oldcontext; if (list != NULL) { - if ((!PySequence_Check(list)) || (PyString_Check(list))) + 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); + PLy_exception_set(PLy_exc_spi_error, "plpy.execute takes a sequence as its second argument"); return NULL; } nargs = PySequence_Length(list); @@ -2067,251 +2523,251 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) if (nargs != plan->nargs) { char *sv; - PyObject *so = PyObject_Str(list); + if (!so) + PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan", + PLy_procedure_name(PLy_curr_procedure)); sv = PyString_AsString(so); - PLy_exception_set(PLy_exc_spi_error, - "Expected sequence of %d arguments, got %d. %s", - plan->nargs, nargs, sv); + PLy_exception_set_plural(PLy_exc_spi_error, + "Expected sequence of %d argument, got %d: %s", + "Expected sequence of %d arguments, got %d: %s", + plan->nargs, + 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) + oldcontext = CurrentMemoryContext; + PG_TRY(); { - nulls = palloc((nargs + 1) * sizeof(char)); + char *nulls = palloc(nargs * sizeof(char)); + volatile int j; - for (i = 0; i < nargs; i++) + for (j = 0; j < nargs; j++) { PyObject *elem, *so; - char *sv; - elem = PySequence_GetItem(list, i); + elem = PySequence_GetItem(list, j); 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)); - + so = PyObject_Str(elem); + if (!so) + PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan", + PLy_procedure_name(PLy_curr_procedure)); + Py_DECREF(elem); + + PG_TRY(); + { + char *sv = PyString_AsString(so); + + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), + sv, + plan->args[j].out.d.typioparam, + -1); + } + PG_CATCH(); + { Py_DECREF(so); - Py_DECREF(elem); + PG_RE_THROW(); + } + PG_END_TRY(); - nulls[i] = ' '; + Py_DECREF(so); + nulls[j] = ' '; } else { Py_DECREF(elem); - plan->values[i] = (Datum) 0; - nulls[i] = 'n'; + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), + NULL, + plan->args[j].out.d.typioparam, + -1); + nulls[j] = 'n'; } } - nulls[i] = '\0'; + + rv = SPI_execute_plan(plan->plan, plan->values, nulls, + PLy_curr_procedure->fn_readonly, limit); + + pfree(nulls); } - else + PG_CATCH(); { - nulls = NULL; - } + int k; + + MemoryContextSwitchTo(oldcontext); + PLy_error_in_progress = CopyErrorData(); + FlushErrorState(); + + /* + * cleanup plan->values array + */ + for (k = 0; k < nargs; k++) + { + if (!plan->args[k].out.d.typbyval && + (plan->values[k] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); + } + } - rv = SPI_execp(plan->plan, plan->values, nulls, limit); - RESTORE_EXC(); + if (!PyErr_Occurred()) + PLy_exception_set(PLy_exc_error, + "unrecognized error in PLy_spi_execute_plan"); + /* XXX this oughta be replaced with errcontext mechanism */ + PLy_elog(WARNING, "in PL/Python function \"%s\"", + PLy_procedure_name(PLy_curr_procedure)); + return NULL; + } + PG_END_TRY(); for (i = 0; i < nargs; i++) { if (!plan->args[i].out.d.typbyval && - (plan->values[i] != (Datum) NULL)) + (plan->values[i] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[i])); - plan->values[i] = (Datum) NULL; + plan->values[i] = PointerGetDatum(NULL); } } if (rv < 0) { PLy_exception_set(PLy_exc_spi_error, - "Unable to execute plan. SPI_execp failed -- %s", - PLy_spi_error_string(rv)); + "SPI_execute_plan failed: %s", + SPI_result_code_string(rv)); return NULL; } return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); } -PyObject * -PLy_spi_execute_query(char *query, int limit) +static PyObject * +PLy_spi_execute_query(char *query, long limit) { - DECLARE_EXC(); int rv; + MemoryContext oldcontext; - SAVE_EXC(); - if (TRAP_EXC()) + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit); + } + PG_CATCH(); { - 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(); + MemoryContextSwitchTo(oldcontext); + PLy_error_in_progress = CopyErrorData(); + FlushErrorState(); + if (!PyErr_Occurred()) + PLy_exception_set(PLy_exc_spi_error, + "unrecognized error in PLy_spi_execute_query"); + /* XXX this oughta be replaced with errcontext mechanism */ + PLy_elog(WARNING, "in PL/Python function \"%s\"", + PLy_procedure_name(PLy_curr_procedure)); + return NULL; } + PG_END_TRY(); - 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)); + "SPI_execute failed: %s", + SPI_result_code_string(rv)); return NULL; } return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); } -PyObject * +static PyObject * PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) { PLyResultObject *result; - - enter(); + MemoryContext oldcontext; 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) + if (status > 0 && tuptable == NULL) { Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); } - else + else if (status > 0 && tuptable != NULL) { - DECLARE_EXC(); PLyTypeInfo args; int i; - PLy_typeinfo_init(&args); Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); + PLy_typeinfo_init(&args); - SAVE_EXC(); - if (TRAP_EXC()) + oldcontext = CurrentMemoryContext; + PG_TRY(); { - 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); - 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); - 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); - PyList_SetItem(result->rows, i, row); + SPI_freetuptable(tuptable); } + } + PG_CATCH(); + { + MemoryContextSwitchTo(oldcontext); + PLy_error_in_progress = CopyErrorData(); + FlushErrorState(); + if (!PyErr_Occurred()) + PLy_exception_set(PLy_exc_error, + "unrecognized error in PLy_spi_execute_fetch_result"); + Py_DECREF(result); PLy_typeinfo_dealloc(&args); - - SPI_freetuptable(tuptable); + return NULL; } - RESTORE_EXC(); + PG_END_TRY(); } 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 +/* + * language handler and interpreter initialization */ +/* + * _PG_init() - library load-time initialization + * + * DO NOT make this static nor change its name! + */ void -PLy_init_all(void) +_PG_init(void) { - static volatile int init_active = 0; + /* Be sure we do initialization only once (should be redundant now) */ + static bool inited = false; - enter(); + if (inited) + return; - if (init_active) - elog(FATAL, "initialization of language module failed"); - init_active = 1; + pg_bindtextdomain(TEXTDOMAIN); Py_Initialize(); PLy_init_interp(); @@ -2322,31 +2778,27 @@ PLy_init_all(void) if (PLy_procedure_cache == NULL) PLy_elog(ERROR, "could not create procedure cache"); - PLy_first_call = 0; - - leave(); + inited = true; } -void +static 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."); + 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())) + if (PLy_interp_globals == NULL || PyErr_Occurred()) PLy_elog(ERROR, "could not initialize globals"); } -void +static void PLy_init_plpy(void) { PyObject *main_mod, @@ -2355,12 +2807,14 @@ PLy_init_plpy(void) PyObject *plpy, *plpy_dict; - enter(); - /* * initialize plpy module */ - PLy_PlanType.ob_type = PLy_ResultType.ob_type = &PyType_Type; + if (PyType_Ready(&PLy_PlanType) < 0) + elog(ERROR, "could not initialize PLy_PlanType"); + if (PyType_Ready(&PLy_ResultType) < 0) + elog(ERROR, "could not initialize PLy_ResultType"); + plpy = Py_InitModule("plpy", PLy_methods); plpy_dict = PyModule_GetDict(plpy); @@ -2381,137 +2835,111 @@ PLy_init_plpy(void) plpy_mod = PyImport_AddModule("plpy"); PyDict_SetItemString(main_dict, "plpy", plpy_mod); if (PyErr_Occurred()) - elog(ERROR, "could not init plpy"); + elog(ERROR, "could not initialize plpy"); } /* the python interface to the elog function * don't confuse these with PLy_elog */ -static PyObject *PLy_output(int, PyObject *, PyObject *); +static PyObject *PLy_output(volatile int, PyObject *, PyObject *); -PyObject * +static PyObject * PLy_debug(PyObject * self, PyObject * args) { return PLy_output(DEBUG2, self, args); } -PyObject * +static PyObject * PLy_log(PyObject * self, PyObject * args) { return PLy_output(LOG, self, args); } -PyObject * +static PyObject * PLy_info(PyObject * self, PyObject * args) { return PLy_output(INFO, self, args); } -PyObject * +static PyObject * PLy_notice(PyObject * self, PyObject * args) { return PLy_output(NOTICE, self, args); } -PyObject * +static PyObject * PLy_warning(PyObject * self, PyObject * args) { return PLy_output(WARNING, self, args); } -PyObject * +static PyObject * PLy_error(PyObject * self, PyObject * args) { return PLy_output(ERROR, self, args); } -PyObject * +static PyObject * PLy_fatal(PyObject * self, PyObject * args) { return PLy_output(FATAL, self, args); } -PyObject * +static 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"); + MemoryContext oldcontext; so = PyObject_Str(args); - if ((so == NULL) || ((sv = PyString_AsString(so)) == NULL)) + if (so == NULL || ((sv = PyString_AsString(so)) == NULL)) { level = ERROR; - sv = "Unable to parse error message in `plpy.elog'"; + sv = dgettext(TEXTDOMAIN, "could not 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) + oldcontext = CurrentMemoryContext; + PG_TRY(); { - PyErr_SetString(PLy_exc_fatal, sv); - return NULL; + elog(level, "%s", sv); } - - /* - * 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()) + PG_CATCH(); { - RESTORE_EXC(); - + MemoryContextSwitchTo(oldcontext); + PLy_error_in_progress = CopyErrorData(); + FlushErrorState(); Py_XDECREF(so); /* - * the real error message should already be written into the - * postgresql log, no? whatever, this shouldn't happen so die - * hideously. + * returning NULL here causes the python interpreter to bail. when + * control passes back to PLy_procedure_call, we check for PG + * exceptions and re-throw the error. */ - elog(FATAL, "elog threw an unknown exception"); - RERAISE_EXC(); + PyErr_SetString(PLy_exc_error, sv); + return NULL; } - - elog(level, "%s", sv); - - RESTORE_EXC(); + PG_END_TRY(); Py_XDECREF(so); - Py_INCREF(Py_None); /* - * return a legal object so the interpreter will continue on its merry - * way + * return a legal object so the interpreter will continue on its merry way */ + Py_INCREF(Py_None); 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 ) + * Get the name of the last procedure 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 + * NB: this returns the SQL name, not the internal Python procedure name */ - -char * +static char * PLy_procedure_name(PLyProcedure * proc) { if (proc == NULL) @@ -2519,74 +2947,90 @@ PLy_procedure_name(PLyProcedure * proc) return proc->proname; } -/* output a python traceback/exception via the postgresql elog - * function. not pretty. +/* + * Call PyErr_SetString with a vprint interface and translation support */ - -static char *PLy_traceback(int *); -static char *PLy_vprintf(const char *fmt, va_list ap); -static char *PLy_printf(const char *fmt,...); - -void +static 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); + vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap); va_end(ap); PyErr_SetString(exc, buf); } -void -PLy_elog(int elevel, const char *fmt,...) +/* + * The same, pluralized. + */ +static void +PLy_exception_set_plural(PyObject *exc, + const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) { - DECLARE_EXC(); + char buf[1024]; va_list ap; - char *xmsg, - *emsg; - int xlevel; - enter(); + va_start(ap, n); + vsnprintf(buf, sizeof(buf), + dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), + ap); + va_end(ap); - xmsg = PLy_traceback(&xlevel); + PyErr_SetString(exc, buf); +} - va_start(ap, fmt); - emsg = PLy_vprintf(fmt, ap); - va_end(ap); +/* Emit a PG error or notice, together with any available info about the + * current Python error. This should be used to propagate Python errors + * into PG. + */ +static void +PLy_elog(int elevel, const char *fmt,...) +{ + char *xmsg; + int xlevel; + StringInfoData emsg; + + xmsg = PLy_traceback(&xlevel); - SAVE_EXC(); - if (TRAP_EXC()) + initStringInfo(&emsg); + for (;;) { - RESTORE_EXC(); - mark(); + va_list ap; + bool success; - /* - * elog called siglongjmp. cleanup, restore and reraise - */ - PLy_restart_in_progress += 1; - PLy_free(emsg); - if (xmsg) - PLy_free(xmsg); - RERAISE_EXC(); + va_start(ap, fmt); + success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + if (success) + break; + enlargeStringInfo(&emsg, emsg.maxlen); } - ereport(elevel, - (errmsg("plpython: %s", emsg), - (xmsg) ? errdetail("%s", xmsg) : 0)); + PG_TRY(); + { + ereport(elevel, + (errmsg("PL/Python: %s", emsg.data), + (xmsg) ? errdetail("%s", xmsg) : 0)); + } + PG_CATCH(); + { + pfree(emsg.data); + if (xmsg) + pfree(xmsg); + PG_RE_THROW(); + } + PG_END_TRY(); - PLy_free(emsg); + pfree(emsg.data); if (xmsg) - PLy_free(xmsg); - - leave(); - - RESTORE_EXC(); + pfree(xmsg); } -char * +static char * PLy_traceback(int *xlevel) { PyObject *e, @@ -2595,10 +3039,8 @@ PLy_traceback(int *xlevel) PyObject *eob, *vob = NULL; char *vstr, - *estr, - *xstr = NULL; - - enter(); + *estr; + StringInfoData xstr; /* * get the current exception @@ -2615,84 +3057,46 @@ PLy_traceback(int *xlevel) } PyErr_NormalizeException(&e, &v, &tb); + Py_XDECREF(tb); eob = PyObject_Str(e); - if ((v) && ((vob = PyObject_Str(v)) != NULL)) + if (v && ((vob = PyObject_Str(v)) != NULL)) vstr = PyString_AsString(vob); else - vstr = "Unknown"; + vstr = "unknown"; - estr = PyString_AsString(eob); - xstr = PLy_printf("%s: %s", estr, vstr); + /* + * I'm not sure what to do if eob is NULL here -- we can't call PLy_elog + * because that function calls us, so we could end up with infinite + * recursion. I'm not even sure if eob could be NULL here -- would an + * Assert() be more appropriate? + */ + estr = eob ? PyString_AsString(eob) : "unrecognized exception"; + initStringInfo(&xstr); + appendStringInfo(&xstr, "%s: %s", estr, vstr); Py_DECREF(eob); Py_XDECREF(vob); + Py_XDECREF(v); /* - * intuit an appropriate error level for based on the exception type + * intuit an appropriate error level based on the exception type */ - if ((PLy_exc_error) && (PyErr_GivenExceptionMatches(e, PLy_exc_error))) + if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error)) *xlevel = ERROR; - else if ((PLy_exc_fatal) && (PyErr_GivenExceptionMatches(e, PLy_exc_fatal))) + 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; + Py_DECREF(e); + return xstr.data; } -/* python module code - */ - - -/* some dumb utility functions - */ +/* python module code */ -void * +/* some dumb utility functions */ +static void * PLy_malloc(size_t bytes) { void *ptr = malloc(bytes); @@ -2704,21 +3108,30 @@ PLy_malloc(size_t bytes) return ptr; } -void * -PLy_realloc(void *optr, size_t bytes) +static void * +PLy_malloc0(size_t bytes) { - void *nptr = realloc(optr, bytes); + void *ptr = PLy_malloc(bytes); - if (nptr == NULL) - ereport(FATAL, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); - return nptr; + MemSet(ptr, 0, bytes); + return ptr; } -/* define this away - */ -void +static char * +PLy_strdup(const char *str) +{ + char *result; + size_t len; + + len = strlen(str) + 1; + result = PLy_malloc(len); + memcpy(result, str, len); + + return result; +} + +/* define this away */ +static void PLy_free(void *ptr) { free(ptr);