X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fpl%2Fplpython%2Fplpython.c;h=345f3f65236619d6cd431fde70a9aaef4eabe709;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=5826ad48c620c10dda02b6d5a1a06b13eba6e61a;hpb=fa8eb8a726b2e77821c8107b23e6b46c605956da;p=postgresql diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 5826ad48c6..345f3f6523 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,40 +1,46 @@ /********************************************************************** * plpython.c - python as a procedural language for PostgreSQL * - * This software is copyright by Andrew Bosma - * but is really shameless cribbed from pltcl.c by Jan Weick, and - * plperl.c by Mark Hollomon. - * - * The author hereby grants permission to use, copy, modify, - * distribute, and license this software and its documentation for any - * purpose, provided that existing copyright notices are retained in - * all copies and that this notice is included verbatim in any - * distributions. No written agreement, license, or royalty fee is - * required for any of the authorized uses. Modifications to this - * software may be copyrighted by their author and need not follow the - * licensing terms described here, provided that the new terms are - * clearly indicated on the first page of each file where they apply. - * - * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY - * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES - * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY - * DERIVATIVES THEREOF, EVEN IF THE AUTHOR HAVE BEEN ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND - * NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, - * AND THE AUTHOR AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE - * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - * - * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.57 2004/09/19 23:38:21 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.121 2009/06/04 18:33:08 tgl Exp $ * ********************************************************************* */ +#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 + +/* + * 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. + */ +#if PY_VERSION_HEX < 0x02030000 +#define PyBool_FromLong(x) PyInt_FromLong(x) +#endif + + #include "postgres.h" /* system stuff */ @@ -42,22 +48,31 @@ #include /* postgreSQL stuff */ -#include "access/heapam.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" #include "executor/spi.h" +#include "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 +PG_MODULE_MAGIC; + /* convert Postgresql Datum or tuple into a PyObject. * input to Python. Tuples are converted to dictionary * objects. @@ -68,7 +83,8 @@ typedef PyObject *(*PLyDatumToObFunc) (const char *); typedef struct PLyDatumToOb { PLyDatumToObFunc func; - FmgrInfo typfunc; + FmgrInfo typfunc; /* The type's output function */ + Oid typoid; /* The OID of the type */ Oid typioparam; bool typbyval; } PLyDatumToOb; @@ -90,7 +106,8 @@ typedef union PLyTypeInput */ typedef struct PLyObToDatum { - FmgrInfo typfunc; + FmgrInfo typfunc; /* The type's input function */ + Oid typoid; /* The OID of the type */ Oid typioparam; bool typbyval; } PLyObToDatum; @@ -117,35 +134,36 @@ typedef struct PLyTypeInfo 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 + * 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; + ItemPointerData fn_tid; bool fn_readonly; - PLyTypeInfo result; /* also used to store info for trigger - * tuple type */ + 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 @@ -166,56 +184,54 @@ typedef struct PLyResultObject } PLyResultObject; -/* function declarations - */ +/* function declarations */ /* Two exported functions: first is the magic telling Postgresql - * what function call interface it implements. Second allows - * preinitialization of the interpreter during postmaster startup. + * what function call interface it implements. Second is for + * initialization of the interpreter during library load. */ Datum plpython_call_handler(PG_FUNCTION_ARGS); -void plpython_init(void); +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); -/* 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 - */ -static void PLy_elog(int, const char *,...); +/* some utility functions */ +static void PLy_elog(int, const char *,...) +__attribute__((format(printf, 2, 3))); static char *PLy_traceback(int *); -static char *PLy_vprintf(const char *fmt, va_list ap); -static char *PLy_printf(const char *fmt,...); 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 *, @@ -226,9 +242,8 @@ static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *); static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid); -static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo, - Oid tgreloid, - HeapTuple procTup, char *key); +static 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 *); @@ -243,8 +258,7 @@ 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 *); @@ -252,10 +266,9 @@ 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 HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *); +static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *); +static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *); /* * Currently active plpython function @@ -279,14 +292,12 @@ 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" }; @@ -324,10 +335,8 @@ plpython_call_handler(PG_FUNCTION_ARGS) PLyProcedure *save_curr_proc; PLyProcedure *volatile proc = NULL; - PLy_init_all(); - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "could not connect to SPI manager"); + elog(ERROR, "SPI_connect failed"); save_curr_proc = PLy_curr_procedure; @@ -339,7 +348,7 @@ plpython_call_handler(PG_FUNCTION_ARGS) HeapTuple trv; proc = PLy_procedure_get(fcinfo, - RelationGetRelid(tdata->tg_relation)); + RelationGetRelid(tdata->tg_relation)); PLy_curr_procedure = proc; trv = PLy_trigger_handler(fcinfo, proc); retval = PointerGetDatum(trv); @@ -410,7 +419,10 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) char *srv; if (!PyString_Check(plrv)) - elog(ERROR, "expected trigger to return None or a String"); + 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) @@ -419,20 +431,23 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) { TriggerData *tdata = (TriggerData *) fcinfo->context; - if ((TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) || - (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))) + 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"); + ereport(WARNING, + (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); } else if (pg_strcasecmp(srv, "OK") != 0) { /* - * hmmm, perhaps they only read the pltcl page, not a - * surprising thing since i've written no documentation, - * so accept a belated OK + * accept "OK" as an alternative to None; otherwise, raise an + * error */ - elog(ERROR, "expected return to be \"SKIP\" or \"MODIFY\""); + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); } } } @@ -478,9 +493,11 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, PG_TRY(); { if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) - elog(ERROR, "TD[\"new\"] deleted, unable to modify tuple"); + ereport(ERROR, + (errmsg("TD[\"new\"] deleted, cannot modify row"))); if (!PyDict_Check(plntup)) - elog(ERROR, "TD[\"new\"] is not a dictionary object"); + ereport(ERROR, + (errmsg("TD[\"new\"] is not a dictionary"))); Py_INCREF(plntup); plkeys = PyDict_Keys(plntup); @@ -498,30 +515,41 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, platt = PyList_GetItem(plkeys, i); if (!PyString_Check(platt)) - elog(ERROR, "attribute name is not a string"); + 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) - elog(ERROR, "invalid attribute \"%s\" in tuple", - PyString_AsString(platt)); + 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"); + elog(FATAL, "Python interpreter is probably corrupted"); Py_INCREF(plval); modattrs[i] = attn; - if (plval != Py_None && !tupdesc->attrs[atti]->attisdropped) + 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] = FunctionCall3(&proc->result.out.r.atts[atti].typfunc, - CStringGetDatum(src), - ObjectIdGetDatum(proc->result.out.r.atts[atti].typioparam), - Int32GetDatum(tupdesc->attrs[atti]->atttypmod)); + 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); @@ -529,7 +557,11 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, } else { - modvalues[i] = (Datum) 0; + 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'; } @@ -540,13 +572,12 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs, modvalues, modnulls); if (rtup == NULL) - elog(ERROR, "SPI_modifytuple failed -- error %d", SPI_result); + elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result); } PG_CATCH(); { Py_XDECREF(plntup); Py_XDECREF(plkeys); - Py_XDECREF(platt); Py_XDECREF(plval); Py_XDECREF(plstr); @@ -579,7 +610,9 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple * *pltevent, *pltwhen, *pltlevel, - *pltrelid; + *pltrelid, + *plttablename, + *plttableschema; PyObject *pltargs, *pytnew, *pytold; @@ -590,19 +623,32 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple * { pltdata = PyDict_New(); if (!pltdata) - PLy_elog(ERROR, "could not build arguments for trigger procedure"); + 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))); + 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)) @@ -682,6 +728,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple * 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); @@ -733,8 +781,7 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple * -/* function handler and friends - */ +/* function handler and friends */ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) { @@ -746,40 +793,158 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) PG_TRY(); { - plargs = PLy_function_build_args(fcinfo, proc); - plrv = PLy_procedure_call(proc, "args", plargs); + 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) - Assert(plrv != NULL); - Assert(!PLy_error_in_progress); + /* + * SETOF function parameters will be deleted when last row is + * returned + */ + PLy_function_delete_args(proc); + Assert(plrv != NULL); + Assert(!PLy_error_in_progress); + } /* - * 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). + * Disconnect from SPI manager and then create the return values datum + * (if the input function does a palloc for it this must not be + * allocated in the SPI memory context because SPI_finish would free + * it). */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); + if (proc->is_setof) + { + bool has_error = false; + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + 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."))); + } + + /* Fetch next from iterator */ + plrv = PyIter_Next(proc->setof); + if (plrv) + rsi->isDone = ExprMultipleResult; + else + { + rsi->isDone = ExprEndResult; + has_error = PyErr_Occurred() != NULL; + } + + 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; + } + } + /* - * convert the python PyObject to a postgresql Datum + * 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). */ - if (plrv == Py_None) + 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"))); + + fcinfo->isnull = false; + rv = (Datum) 0; + } + else if (plrv == Py_None) { fcinfo->isnull = true; - rv = (Datum) NULL; + 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 (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 = FunctionCall3(&proc->result.out.d.typfunc, - PointerGetDatum(plrv_sc), - ObjectIdGetDatum(proc->result.out.d.typioparam), - Int32GetDatum(-1)); + rv = InputFunctionCall(&proc->result.out.d.typfunc, + plrv_sc, + proc->result.out.d.typioparam, + -1); } - } PG_CATCH(); { @@ -808,8 +973,8 @@ PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs) proc->globals, proc->globals); /* - * If there was an error in a PG callback, propagate that no matter - * what Python claims about its success. + * If there was an error in a PG callback, propagate that no matter what + * Python claims about its success. */ if (PLy_error_in_progress) { @@ -819,10 +984,10 @@ PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs) ReThrowError(edata); } - if ((rv == NULL) || (PyErr_Occurred())) + if (rv == NULL || PyErr_Occurred()) { Py_XDECREF(rv); - PLy_elog(ERROR, "function \"%s\" failed", proc->proname); + PLy_elog(ERROR, "PL/Python function \"%s\" failed", proc->proname); } return rv; @@ -867,6 +1032,7 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) tmptup.t_data = td; arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); + ReleaseTupleDesc(tupdesc); } } else @@ -876,13 +1042,9 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) else { char *ct; - Datum dt; - dt = FunctionCall3(&(proc->args[i].in.d.typfunc), - fcinfo->arg[i], - ObjectIdGetDatum(proc->args[i].in.d.typioparam), - Int32GetDatum(-1)); - ct = DatumGetCString(dt); + ct = OutputFunctionCall(&(proc->args[i].in.d.typfunc), + fcinfo->arg[i]); arg = (proc->args[i].in.d.func) (ct); pfree(ct); } @@ -894,10 +1056,13 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) arg = Py_None; } - /* - * FIXME -- error check this - */ - PyList_SetItem(args, i, arg); + 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(); @@ -913,6 +1078,20 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) } +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 */ @@ -940,7 +1119,7 @@ PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) elog(ERROR, "cache lookup failed for function %u", fn_oid); rv = snprintf(key, sizeof(key), "%u_%u", fn_oid, tgreloid); - if ((rv >= sizeof(key)) || (rv < 0)) + if (rv >= sizeof(key) || rv < 0) elog(ERROR, "key too long"); plproc = PyDict_GetItemString(PLy_procedure_cache, key); @@ -956,7 +1135,7 @@ PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) 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; @@ -964,7 +1143,24 @@ PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) } if (proc == NULL) - proc = PLy_procedure_create(fcinfo, tgreloid, 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); @@ -972,8 +1168,7 @@ PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) } static PLyProcedure * -PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, - HeapTuple procTup, char *key) +PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; @@ -990,23 +1185,21 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u_trigger_%u", NameStr(procStruct->proname), - fcinfo->flinfo->fn_oid, + HeapTupleGetOid(procTup), tgreloid); else rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u", NameStr(procStruct->proname), - fcinfo->flinfo->fn_oid); - if ((rv >= sizeof(procName)) || (rv < 0)) + 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); @@ -1016,97 +1209,141 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, proc->nargs = 0; proc->code = proc->statics = NULL; proc->globals = proc->me = NULL; + proc->is_setof = procStruct->proretset; + proc->setof = NULL; + proc->argnames = NULL; PG_TRY(); { /* - * get information required for output conversion of the return - * value, but only if this isn't a trigger. + * get information required for output conversion of the return value, + * but only if this isn't a trigger. */ - if (!CALLED_AS_TRIGGER(fcinfo)) + if (!OidIsValid(tgreloid)) { HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; rvTypeTup = SearchSysCache(TYPEOID, - ObjectIdGetDatum(procStruct->prorettype), + 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 */ - if (rvTypeStruct->typtype == 'p') + /* 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 may only be called as triggers"))); + errmsg("trigger functions can only be called as triggers"))); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpython functions cannot return type %s", - format_type_be(procStruct->prorettype)))); + errmsg("PL/Python functions cannot return type %s", + format_type_be(procStruct->prorettype)))); } - if (rvTypeStruct->typtype == 'c') - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpython functions cannot return tuples yet"))); + 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); ReleaseSysCache(rvTypeTup); } - else - { - /* - * input/output conversion for trigger tuples. use the result - * TypeInfo variable to store the tuple conversion info. - */ - TriggerData *tdata = (TriggerData *) fcinfo->context; - - PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - } /* - * now get information required for input conversion of the - * procedures arguments. + * 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. */ - proc->nargs = fcinfo->nargs; - for (i = 0; i < fcinfo->nargs; i++) + if (procStruct->pronargs) { - HeapTuple argTypeTup; - Form_pg_type argTypeStruct; + 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)++; + } + } - 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); + proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs); + for (i = pos = 0; i < total; i++) + { + HeapTuple argTypeTup; + Form_pg_type argTypeStruct; - /* Disallow pseudotype argument */ - if (argTypeStruct->typtype == 'p') - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpython functions cannot take type %s", - format_type_be(procStruct->proargtypes[i])))); - - if (argTypeStruct->typtype != 'c') - PLy_input_datum_func(&(proc->args[i]), - procStruct->proargtypes[i], - argTypeTup); - else - proc->args[i].is_rowtype = 2; /* still need to set I/O - * funcs */ + if (modes && + (modes[i] == PROARGMODE_OUT || + modes[i] == PROARGMODE_TABLE)) + continue; /* skip OUT arguments */ - ReleaseSysCache(argTypeTup); - } + Assert(types[i] == procStruct->proargtypes.values[pos]); + + 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); + + /* 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; + } + + /* get argument name */ + proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL; + + ReleaseSysCache(argTypeTup); + pos++; + } + } /* * get the text of the function. @@ -1115,8 +1352,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); - procSource = DatumGetCString(DirectFunctionCall1(textout, - prosrcdatum)); + procSource = TextDatumGetCString(prosrcdatum); PLy_procedure_compile(proc, procSource); @@ -1147,8 +1383,8 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src) 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); @@ -1160,7 +1396,7 @@ 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]; @@ -1171,16 +1407,16 @@ 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); } static char * @@ -1206,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++; @@ -1238,6 +1478,7 @@ PLy_procedure_delete(PLyProcedure * proc) if (proc->pyname) PLy_free(proc->pyname); for (i = 0; i < proc->nargs; i++) + { if (proc->args[i].is_rowtype == 1) { if (proc->args[i].in.r.atts) @@ -1245,10 +1486,16 @@ PLy_procedure_delete(PLyProcedure * proc) if (proc->args[i].out.r.atts) PLy_free(proc->args[i].out.r.atts); } + 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. */ static void PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) @@ -1257,10 +1504,15 @@ PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); - arg->is_rowtype = 1; - arg->in.r.natts = desc->natts; - arg->in.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb)); + + 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++) { @@ -1269,8 +1521,11 @@ PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) 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), + ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", @@ -1291,10 +1546,15 @@ PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) if (arg->is_rowtype == 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); - arg->is_rowtype = 1; - arg->out.r.natts = desc->natts; - arg->out.r.atts = malloc(desc->natts * sizeof(PLyDatumToOb)); + + 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++) { @@ -1303,8 +1563,11 @@ PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) 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), + ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", @@ -1331,6 +1594,7 @@ PLy_output_datum_func2(PLyObToDatum * arg, HeapTuple typeTup) Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); perm_fmgr_info(typeStruct->typinput, &arg->typfunc); + arg->typoid = HeapTupleGetOid(typeTup); arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; } @@ -1351,6 +1615,7 @@ PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, HeapTuple typeTup) /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); + arg->typoid = HeapTupleGetOid(typeTup); arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; @@ -1399,14 +1664,19 @@ PLy_typeinfo_dealloc(PLyTypeInfo * arg) } } -/* assumes that a bool is always returned as a 't' or 'f' - */ +/* assumes that a bool is always returned as a 't' or 'f' */ static PyObject * PLyBool_FromString(const char *src) { + /* + * 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); } static PyObject * @@ -1417,7 +1687,7 @@ PLyFloat_FromString(const char *src) errno = 0; v = strtod(src, &eptr); - if ((*eptr != '\0') || (errno)) + if (*eptr != '\0' || errno) return NULL; return PyFloat_FromDouble(v); } @@ -1430,7 +1700,7 @@ PLyInt_FromString(const char *src) errno = 0; v = strtol(src, &eptr, 0); - if ((*eptr != '\0') || (errno)) + if (*eptr != '\0' || errno) return NULL; return PyInt_FromLong(v); } @@ -1458,7 +1728,7 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) dict = PyDict_New(); if (dict == NULL) - PLy_elog(ERROR, "could not create tuple dictionary"); + PLy_elog(ERROR, "could not create new dictionary"); PG_TRY(); { @@ -1466,8 +1736,7 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) { char *key, *vsrc; - Datum vattr, - vdat; + Datum vattr; bool is_null; PyObject *value; @@ -1477,15 +1746,12 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) 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)) + if (is_null || info->in.r.atts[i].func == NULL) PyDict_SetItemString(dict, key, Py_None); else { - vdat = FunctionCall3(&info->in.r.atts[i].typfunc, - vattr, - ObjectIdGetDatum(info->in.r.atts[i].typioparam), - Int32GetDatum(desc->attrs[i]->atttypmod)); - vsrc = DatumGetCString(vdat); + vsrc = OutputFunctionCall(&info->in.r.atts[i].typfunc, + vattr); /* * no exceptions allowed @@ -1507,11 +1773,250 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) return dict; } -/* initialization, some python variables function declared here - */ -/* interface to postgresql elog - */ +static HeapTuple +PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) +{ + TupleDesc desc; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + Assert(PyMapping_Check(mapping)); + + 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 = 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(); + } + + 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(); + { + 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; +} + + +/* 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 *); @@ -1520,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 *); @@ -1531,24 +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 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); @@ -1562,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 */ @@ -1577,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 = { @@ -1607,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 */ @@ -1622,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[] = { /* @@ -1662,8 +2156,7 @@ static PyMethodDef PLy_methods[] = { }; -/* plan object methods - */ +/* plan object methods */ static PyObject * PLy_plan_new(void) { @@ -1699,7 +2192,7 @@ PLy_plan_dealloc(PyObject * arg) PLy_free(ob->args); } - PyMem_DEL(arg); + arg->ob_type->tp_free(arg); } @@ -1718,14 +2211,13 @@ 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 */ static PyObject * PLy_result_new(void) @@ -1754,21 +2246,13 @@ PLy_result_dealloc(PyObject * arg) Py_XDECREF(ob->rows); Py_XDECREF(ob->status); - PyMem_DEL(ob); + arg->ob_type->tp_free(arg); } static PyObject * -PLy_result_getattr(PyObject * self, char *attr) +PLy_result_getattr(PyObject * self, char *name) { - return NULL; -} - -#ifdef NOT_USED -/* Appear to be unused */ -static PyObject * -PLy_result_fetch(PyObject * self, PyObject * args) -{ - return NULL; + return Py_FindMethod(PLy_result_methods, self, name); } static PyObject * @@ -1788,9 +2272,8 @@ PLy_result_status(PyObject * self, PyObject * args) Py_INCREF(ob->status); return ob->status; } -#endif -static int +static Py_ssize_t PLy_result_length(PyObject * arg) { PLyResultObject *ob = (PLyResultObject *) arg; @@ -1799,7 +2282,7 @@ PLy_result_length(PyObject * arg) } static PyObject * -PLy_result_item(PyObject * arg, int idx) +PLy_result_item(PyObject * arg, Py_ssize_t idx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1811,7 +2294,7 @@ PLy_result_item(PyObject * arg, int idx) } static int -PLy_result_ass_item(PyObject * arg, int idx, PyObject * item) +PLy_result_ass_item(PyObject * arg, Py_ssize_t idx, PyObject * item) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1822,7 +2305,7 @@ PLy_result_ass_item(PyObject * arg, int idx, PyObject * item) } static PyObject * -PLy_result_slice(PyObject * arg, int lidx, int hidx) +PLy_result_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1835,7 +2318,7 @@ PLy_result_slice(PyObject * arg, int lidx, int hidx) } static int -PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice) +PLy_result_ass_slice(PyObject * arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject * slice) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; @@ -1844,8 +2327,7 @@ PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice) return rv; } -/* SPI interface - */ +/* SPI interface */ static PyObject * PLy_spi_prepare(PyObject * self, PyObject * args) { @@ -1859,21 +2341,21 @@ PLy_spi_prepare(PyObject * self, PyObject * args) /* Can't execute more if we have an unhandled error */ if (PLy_error_in_progress) { - PyErr_SetString(PLy_exc_error, "Transaction aborted."); + 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; } @@ -1898,40 +2380,54 @@ PLy_spi_prepare(PyObject * self, PyObject * args) /* * the other loop might throw an exception, if PLyTypeInfo - * member isn't properly initialized the Py_DECREF(plan) - * will go boom + * 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; + plan->values[i] = PointerGetDatum(NULL); } for (i = 0; i < nargs; i++) { char *sptr; HeapTuple typeTup; + Oid typeId; + int32 typmod; Form_pg_type typeStruct; optr = PySequence_GetItem(list, i); if (!PyString_Check(optr)) - elog(ERROR, "Type names must be strings."); + ereport(ERROR, + (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); sptr = PyString_AsString(optr); - /* - * XXX should extend this to allow qualified type - * names - */ - typeTup = typenameType(makeTypeName(sptr)); + /******************************************************** + * 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] = HeapTupleGetOid(typeTup); + plan->types[i] = typeId; typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - if (typeStruct->typtype != 'c') + if (typeStruct->typtype != TYPTYPE_COMPOSITE) PLy_output_datum_func(&plan->args[i], typeTup); else - elog(ERROR, "tuples not handled in plpy.prepare, yet."); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("plpy.prepare does not support composite types"))); ReleaseSysCache(typeTup); } } @@ -1958,10 +2454,10 @@ PLy_spi_prepare(PyObject * self, PyObject * args) Py_DECREF(plan); Py_XDECREF(optr); if (!PyErr_Occurred()) - PyErr_SetString(PLy_exc_spi_error, - "Unknown error in PLy_spi_prepare"); + 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 function %s:", + PLy_elog(WARNING, "in PL/Python function \"%s\"", PLy_procedure_name(PLy_curr_procedure)); return NULL; } @@ -1979,45 +2475,42 @@ PLy_spi_execute(PyObject * self, PyObject * args) char *query; PyObject *plan; PyObject *list = NULL; - int limit = 0; + long limit = 0; /* Can't execute more if we have an unhandled error */ if (PLy_error_in_progress) { - PyErr_SetString(PLy_exc_error, "Transaction aborted."); + PLy_exception_set(PLy_exc_error, "transaction aborted"); return NULL; } - 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))) + 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; } static PyObject * -PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) +PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) { volatile int nargs; int i, rv; PLyPlanObject *plan; - char *nulls; 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); @@ -2030,13 +2523,17 @@ 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; @@ -2045,39 +2542,52 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) oldcontext = CurrentMemoryContext; PG_TRY(); { - nulls = palloc(nargs * 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); + if (!so) + PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan", + PLy_procedure_name(PLy_curr_procedure)); + Py_DECREF(elem); - /* - * FIXME -- if this elogs, we have Python reference leak - */ - plan->values[i] = - FunctionCall3(&(plan->args[i].out.d.typfunc), - CStringGetDatum(sv), - ObjectIdGetDatum(plan->args[i].out.d.typioparam), - Int32GetDatum(-1)); + PG_TRY(); + { + char *sv = PyString_AsString(so); - Py_DECREF(so); - Py_DECREF(elem); + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), + sv, + plan->args[j].out.d.typioparam, + -1); + } + PG_CATCH(); + { + Py_DECREF(so); + 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'; } } @@ -2088,6 +2598,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) } PG_CATCH(); { + int k; + MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); @@ -2095,21 +2607,21 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) /* * cleanup plan->values array */ - for (i = 0; i < nargs; i++) + for (k = 0; k < nargs; k++) { - if (!plan->args[i].out.d.typbyval && - (plan->values[i] != (Datum) NULL)) + if (!plan->args[k].out.d.typbyval && + (plan->values[k] != PointerGetDatum(NULL))) { - pfree(DatumGetPointer(plan->values[i])); - plan->values[i] = (Datum) NULL; + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); } } if (!PyErr_Occurred()) - PyErr_SetString(PLy_exc_error, - "Unknown error in PLy_spi_execute_plan"); + 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 function %s:", + PLy_elog(WARNING, "in PL/Python function \"%s\"", PLy_procedure_name(PLy_curr_procedure)); return NULL; } @@ -2118,10 +2630,10 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) 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); } } @@ -2137,7 +2649,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit) } static PyObject * -PLy_spi_execute_query(char *query, int limit) +PLy_spi_execute_query(char *query, long limit) { int rv; MemoryContext oldcontext; @@ -2153,10 +2665,10 @@ PLy_spi_execute_query(char *query, int limit) PLy_error_in_progress = CopyErrorData(); FlushErrorState(); if (!PyErr_Occurred()) - PyErr_SetString(PLy_exc_spi_error, - "Unknown error in PLy_spi_execute_query"); + 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 function %s:", + PLy_elog(WARNING, "in PL/Python function \"%s\"", PLy_procedure_name(PLy_curr_procedure)); return NULL; } @@ -2183,24 +2695,19 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) 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) { PLyTypeInfo args; int i; - PLy_typeinfo_init(&args); Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); + PLy_typeinfo_init(&args); oldcontext = CurrentMemoryContext; PG_TRY(); @@ -2214,7 +2721,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) for (i = 0; i < rows; i++) { PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], - tuptable->tupdesc); + tuptable->tupdesc); PyList_SetItem(result->rows, i, row); } @@ -2229,8 +2736,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) PLy_error_in_progress = CopyErrorData(); FlushErrorState(); if (!PyErr_Occurred()) - PyErr_SetString(PLy_exc_error, - "Unknown error in PLy_spi_execute_fetch_result"); + PLy_exception_set(PLy_exc_error, + "unrecognized error in PLy_spi_execute_fetch_result"); Py_DECREF(result); PLy_typeinfo_dealloc(&args); return NULL; @@ -2247,24 +2754,20 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) */ /* - * plpython_init() - Initialize everything that can be - * safely initialized during postmaster - * startup. + * _PG_init() - library load-time initialization * - * DO NOT make this static --- it has to be callable by preload + * DO NOT make this static nor change its name! */ void -plpython_init(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; - /* Do initialization only once */ - if (!PLy_first_call) + if (inited) return; - if (init_active) - elog(FATAL, "initialization of language module failed"); - init_active = 1; + pg_bindtextdomain(TEXTDOMAIN); Py_Initialize(); PLy_init_interp(); @@ -2275,21 +2778,7 @@ plpython_init(void) if (PLy_procedure_cache == NULL) PLy_elog(ERROR, "could not create procedure cache"); - PLy_first_call = 0; -} - -static void -PLy_init_all(void) -{ - /* Execute postmaster-startup safe initialization */ - if (PLy_first_call) - plpython_init(); - - /* - * Any other initialization that must be done each time a new backend - * starts -- currently none - */ - + inited = true; } static void @@ -2298,14 +2787,14 @@ PLy_init_interp(void) PyObject *mainmod; 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"); } @@ -2321,7 +2810,11 @@ PLy_init_plpy(void) /* * 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); @@ -2342,13 +2835,13 @@ 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 *); static PyObject * PLy_debug(PyObject * self, PyObject * args) @@ -2401,10 +2894,10 @@ PLy_output(volatile int level, PyObject * self, PyObject * args) 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"); } oldcontext = CurrentMemoryContext; @@ -2432,8 +2925,7 @@ PLy_output(volatile int level, PyObject * self, PyObject * args) Py_XDECREF(so); /* - * 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; @@ -2441,13 +2933,12 @@ PLy_output(volatile int level, PyObject * self, PyObject * args) /* - * 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 */ - static char * PLy_procedure_name(PLyProcedure * proc) { @@ -2456,8 +2947,8 @@ 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 void PLy_exception_set(PyObject * exc, const char *fmt,...) @@ -2466,7 +2957,27 @@ PLy_exception_set(PyObject * exc, const char *fmt,...) 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); +} + +/* + * The same, pluralized. + */ +static void +PLy_exception_set_plural(PyObject *exc, + const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, n); + vsnprintf(buf, sizeof(buf), + dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), + ap); va_end(ap); PyErr_SetString(exc, buf); @@ -2479,35 +2990,44 @@ PLy_exception_set(PyObject * exc, const char *fmt,...) static void PLy_elog(int elevel, const char *fmt,...) { - va_list ap; - char *xmsg, - *emsg; + char *xmsg; int xlevel; + StringInfoData emsg; xmsg = PLy_traceback(&xlevel); - va_start(ap, fmt); - emsg = PLy_vprintf(fmt, ap); - va_end(ap); + initStringInfo(&emsg); + for (;;) + { + va_list ap; + bool success; + + va_start(ap, fmt); + success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + if (success) + break; + enlargeStringInfo(&emsg, emsg.maxlen); + } PG_TRY(); { ereport(elevel, - (errmsg("plpython: %s", emsg), + (errmsg("PL/Python: %s", emsg.data), (xmsg) ? errdetail("%s", xmsg) : 0)); } PG_CATCH(); { - PLy_free(emsg); + pfree(emsg.data); if (xmsg) - PLy_free(xmsg); + pfree(xmsg); PG_RE_THROW(); } PG_END_TRY(); - PLy_free(emsg); + pfree(emsg.data); if (xmsg) - PLy_free(xmsg); + pfree(xmsg); } static char * @@ -2519,8 +3039,8 @@ PLy_traceback(int *xlevel) PyObject *eob, *vob = NULL; char *vstr, - *estr, - *xstr = NULL; + *estr; + StringInfoData xstr; /* * get the current exception @@ -2537,81 +3057,45 @@ 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; - return xstr; + Py_DECREF(e); + return xstr.data; } -static 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; -} - -static char * -PLy_vprintf(const char *fmt, va_list ap) -{ - size_t blen; - int bchar, - tries = 2; - char *buf; - - blen = strlen(fmt) * 2; - if (blen < 256) - blen = 256; - buf = PLy_malloc(blen * sizeof(char)); - - while (1) - { - bchar = vsnprintf(buf, blen, fmt, ap); - if ((bchar > 0) && (bchar < blen)) - return buf; - if (tries-- <= 0) - break; - if (blen > 0) - blen = bchar + 1; - else - blen *= 2; - buf = PLy_realloc(buf, blen); - } - PLy_free(buf); - return NULL; -} - -/* python module code - */ - - -/* some dumb utility functions - */ +/* python module code */ +/* some dumb utility functions */ static void * PLy_malloc(size_t bytes) { @@ -2625,19 +3109,28 @@ PLy_malloc(size_t bytes) } static void * -PLy_realloc(void *optr, size_t bytes) +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 - */ +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) {