X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fpl%2Fplpython%2Fplpython.c;h=345f3f65236619d6cd431fde70a9aaef4eabe709;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=31ce60373f61555bde119b9e270b4c35a151dbed;hpb=57690c6803525f879fe96920a05e979ece073e71;p=postgresql diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 31ce60373f..345f3f6523 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,7 +1,7 @@ /********************************************************************** * plpython.c - python as a procedural language for PostgreSQL * - * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.97 2007/04/02 03:49:42 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.121 2009/06/04 18:33:08 tgl Exp $ * ********************************************************************* */ @@ -23,6 +23,24 @@ #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 */ @@ -30,13 +48,13 @@ #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" @@ -46,6 +64,10 @@ #include "utils/syscache.h" #include "utils/typcache.h" +/* define our text domain for translations */ +#undef TEXTDOMAIN +#define TEXTDOMAIN PG_TEXTDOMAIN("plpython") + #include #include @@ -61,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; @@ -181,22 +204,25 @@ PG_FUNCTION_INFO_V1(plpython_call_handler); 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 */ static char *PLy_procedure_name(PLyProcedure *); /* some utility functions */ -static void PLy_elog(int, const char *,...); +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 *); @@ -216,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 *); @@ -311,7 +336,7 @@ plpython_call_handler(PG_FUNCTION_ARGS) PLyProcedure *volatile proc = NULL; 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; @@ -397,7 +422,7 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), - errdetail("Expected None or a String."))); + errdetail("Expected None or a string."))); srv = PyString_AsString(plrv); if (pg_strcasecmp(srv, "SKIP") == 0) @@ -410,7 +435,8 @@ PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) 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) { @@ -467,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); @@ -487,16 +515,18 @@ 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); @@ -511,7 +541,7 @@ PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, { plstr = PyObject_Str(plval); if (!plstr) - PLy_elog(ERROR, "function \"%s\" could not modify tuple", + 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); @@ -542,7 +572,7 @@ 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(); { @@ -593,7 +623,7 @@ 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); @@ -698,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); @@ -799,7 +831,8 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only value per call is allowed"))); + errmsg("unsupported set function return mode"), + errdetail("PL/Python set-returning functions only support returning only value per call."))); } rsi->returnMode = SFRM_ValuePerCall; @@ -812,7 +845,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("returned object cannot be iterated"), - errdetail("SETOF must be returned as iterable object"))); + errdetail("PL/Python set-returning functions must return an iterable object."))); } /* Fetch next from iterator */ @@ -858,8 +891,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) if (plrv != Py_None) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("invalid return value from plpython function"), - errdetail("Functions returning type \"void\" must return None."))); + errmsg("PL/Python function with return type \"void\" did not return None"))); fcinfo->isnull = false; rv = (Datum) 0; @@ -906,7 +938,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) fcinfo->isnull = false; plrv_so = PyObject_Str(plrv); if (!plrv_so) - PLy_elog(ERROR, "function \"%s\" could not create return value", proc->proname); + 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, @@ -955,7 +987,7 @@ PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs) 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; @@ -1024,10 +1056,12 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) arg = Py_None; } - if (PyList_SetItem(args, i, arg) == -1 || - (proc->argnames && - PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1)) - PLy_elog(ERROR, "problem setting up arguments for \"%s\"", proc->proname); + 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; } } @@ -1053,7 +1087,8 @@ PLy_function_delete_args(PLyProcedure * proc) return; for (i = 0; i < proc->nargs; i++) - PyDict_DelItemString(proc->globals, proc->argnames[i]); + if (proc->argnames[i]) + PyDict_DelItemString(proc->globals, proc->argnames[i]); } @@ -1108,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); @@ -1116,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; @@ -1127,9 +1178,6 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, bool isnull; int i, rv; - Datum argnames; - Datum *elems; - int nelems; procStruct = (Form_pg_proc) GETSTRUCT(procTup); @@ -1137,13 +1185,13 @@ 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); + HeapTupleGetOid(procTup)); if (rv >= sizeof(procName) || rv < 0) elog(ERROR, "procedure name would overrun buffer"); @@ -1171,7 +1219,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, * 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; @@ -1195,7 +1243,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpython functions cannot return type %s", + errmsg("PL/Python functions cannot return type %s", format_type_be(procStruct->prorettype)))); } @@ -1213,69 +1261,88 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, 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 - * procedure's 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; - if (proc->nargs) + if (procStruct->pronargs) { - argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull); - if (!isnull) + 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 { - deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i', - &elems, NULL, &nelems); - if (nelems != proc->nargs) - elog(ERROR, - "proargnames must have the same number of elements " - "as the function has arguments"); - proc->argnames = (char **) PLy_malloc(sizeof(char *) * proc->nargs); + /* proc->nargs was initialized to 0 above */ + for (i = 0; i < total; i++) + { + if (modes[i] != PROARGMODE_OUT && + modes[i] != PROARGMODE_TABLE) + (proc->nargs)++; + } } - } - for (i = 0; i < fcinfo->nargs; i++) - { - HeapTuple argTypeTup; - Form_pg_type argTypeStruct; - argTypeTup = SearchSysCache(TYPEOID, - ObjectIdGetDatum(procStruct->proargtypes.values[i]), - 0, 0, 0); - if (!HeapTupleIsValid(argTypeTup)) - elog(ERROR, "cache lookup failed for type %u", - procStruct->proargtypes.values[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 == TYPTYPE_PSEUDO) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpython functions cannot take type %s", - format_type_be(procStruct->proargtypes.values[i])))); - - if (argTypeStruct->typtype != TYPTYPE_COMPOSITE) - PLy_input_datum_func(&(proc->args[i]), - procStruct->proargtypes.values[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 */ + + 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; + } - ReleaseSysCache(argTypeTup); + /* get argument name */ + proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL; - /* Fetch argument name */ - if (proc->argnames) - proc->argnames[i] = PLy_strdup(DatumGetCString(DirectFunctionCall1(textout, elems[i]))); + ReleaseSysCache(argTypeTup); + + pos++; + } } /* @@ -1285,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); @@ -1350,7 +1416,7 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src) 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 * @@ -1427,8 +1493,9 @@ PLy_procedure_delete(PLyProcedure * proc) 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) @@ -1437,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 = PLy_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++) { @@ -1449,6 +1521,9 @@ 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), 0, 0, 0); @@ -1471,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 = PLy_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++) { @@ -1483,6 +1563,9 @@ 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), 0, 0, 0); @@ -1532,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; @@ -1585,14 +1669,14 @@ 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 + * 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 * @@ -1644,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(); { @@ -1696,8 +1780,8 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) TupleDesc desc; HeapTuple tuple; Datum *values; - char *nulls; - int i; + bool *nulls; + volatile int i; Assert(PyMapping_Check(mapping)); @@ -1708,12 +1792,12 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); - nulls = palloc(sizeof(char) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { char *key; - PyObject *value, - *so; + PyObject *volatile value, + *volatile so; key = NameStr(desc->attrs[i]->attname); value = so = NULL; @@ -1723,7 +1807,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) if (value == Py_None) { values[i] = (Datum) NULL; - nulls[i] = 'n'; + nulls[i] = true; } else if (value) { @@ -1731,7 +1815,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) so = PyObject_Str(value); if (so == NULL) - PLy_elog(ERROR, "cannot convert mapping type"); + PLy_elog(ERROR, "could not compute string representation of Python object"); valuestr = PyString_AsString(so); values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc @@ -1740,14 +1824,14 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) ,-1); Py_DECREF(so); so = NULL; - nulls[i] = ' '; + nulls[i] = false; } else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("no mapping found with key \"%s\"", key), - errhint("to return null in specific column, " - "add value None to map with key named after 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; @@ -1761,7 +1845,7 @@ PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) PG_END_TRY(); } - tuple = heap_formtuple(desc, values, nulls); + tuple = heap_form_tuple(desc, values, nulls); ReleaseTupleDesc(desc); pfree(values); pfree(nulls); @@ -1776,8 +1860,8 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) TupleDesc desc; HeapTuple tuple; Datum *values; - char *nulls; - int i; + bool *nulls; + volatile int i; Assert(PySequence_Check(sequence)); @@ -1790,7 +1874,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) if (PySequence_Length(sequence) != desc->natts) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("returned sequence's length must be same as tuple's length"))); + errmsg("length of returned sequence did not match number of columns in row"))); if (info->is_rowtype == 2) PLy_output_tuple_funcs(info, desc); @@ -1798,11 +1882,11 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); - nulls = palloc(sizeof(char) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { - PyObject *value, - *so; + PyObject *volatile value, + *volatile so; value = so = NULL; PG_TRY(); @@ -1812,7 +1896,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) if (value == Py_None) { values[i] = (Datum) NULL; - nulls[i] = 'n'; + nulls[i] = true; } else if (value) { @@ -1820,7 +1904,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) so = PyObject_Str(value); if (so == NULL) - PLy_elog(ERROR, "cannot convert sequence type"); + 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 @@ -1828,7 +1912,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) ,-1); Py_DECREF(so); so = NULL; - nulls[i] = ' '; + nulls[i] = false; } Py_XDECREF(value); @@ -1843,7 +1927,7 @@ PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) PG_END_TRY(); } - tuple = heap_formtuple(desc, values, nulls); + tuple = heap_form_tuple(desc, values, nulls); ReleaseTupleDesc(desc); pfree(values); pfree(nulls); @@ -1858,8 +1942,8 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) TupleDesc desc; HeapTuple tuple; Datum *values; - char *nulls; - int i; + bool *nulls; + volatile int i; desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1); if (info->is_rowtype == 2) @@ -1868,12 +1952,12 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); - nulls = palloc(sizeof(char) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { char *key; - PyObject *value, - *so; + PyObject *volatile value, + *volatile so; key = NameStr(desc->attrs[i]->attname); value = so = NULL; @@ -1883,7 +1967,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) if (value == Py_None) { values[i] = (Datum) NULL; - nulls[i] = 'n'; + nulls[i] = true; } else if (value) { @@ -1891,7 +1975,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) so = PyObject_Str(value); if (so == NULL) - PLy_elog(ERROR, "cannot convert object type"); + 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 @@ -1899,15 +1983,15 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) ,-1); Py_DECREF(so); so = NULL; - nulls[i] = ' '; + nulls[i] = false; } else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("no attribute named \"%s\"", key), - errhint("to return null in specific column, " - "let returned object to have attribute named " - "after column with value None"))); + 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; @@ -1921,7 +2005,7 @@ PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) PG_END_TRY(); } - tuple = heap_formtuple(desc, values, nulls); + tuple = heap_form_tuple(desc, values, nulls); ReleaseTupleDesc(desc); pfree(values); pfree(nulls); @@ -1953,11 +2037,11 @@ static void PLy_result_dealloc(PyObject *); static PyObject *PLy_result_getattr(PyObject *, char *); static PyObject *PLy_result_nrows(PyObject *, PyObject *); static PyObject *PLy_result_status(PyObject *, PyObject *); -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 *); @@ -1977,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 */ @@ -2001,15 +2085,14 @@ static PyMethodDef PLy_plan_methods[] = { {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 = { @@ -2022,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 */ @@ -2128,7 +2211,7 @@ 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; } @@ -2190,7 +2273,7 @@ PLy_result_status(PyObject * self, PyObject * args) return ob->status; } -static int +static Py_ssize_t PLy_result_length(PyObject * arg) { PLyResultObject *ob = (PLyResultObject *) arg; @@ -2199,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; @@ -2211,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; @@ -2222,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; @@ -2235,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; @@ -2258,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))) { - 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; } @@ -2310,26 +2393,27 @@ PLy_spi_prepare(PyObject * self, PyObject * args) { char *sptr; HeapTuple typeTup; - Oid typeId; - int32 typmod; + 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); - + /******************************************************** - * Resolve argument type names and then look them up by - * oid in the system cache, and remember the required + * 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); + 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", typeId); @@ -2341,7 +2425,9 @@ PLy_spi_prepare(PyObject * self, PyObject * args) 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); } } @@ -2368,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; } @@ -2394,7 +2480,7 @@ PLy_spi_execute(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; } @@ -2407,7 +2493,7 @@ PLy_spi_execute(PyObject * self, PyObject * args) 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; } @@ -2424,9 +2510,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) { 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); @@ -2442,12 +2526,14 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) PyObject *so = PyObject_Str(list); if (!so) - PLy_elog(ERROR, "function \"%s\" could not execute plan", + 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; @@ -2457,18 +2543,19 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) PG_TRY(); { char *nulls = palloc(nargs * sizeof(char)); + volatile int j; - for (i = 0; i < nargs; i++) + for (j = 0; j < nargs; j++) { PyObject *elem, *so; - elem = PySequence_GetItem(list, i); + elem = PySequence_GetItem(list, j); if (elem != Py_None) { so = PyObject_Str(elem); if (!so) - PLy_elog(ERROR, "function \"%s\" could not execute plan", + PLy_elog(ERROR, "PL/Python function \"%s\" could not execute plan", PLy_procedure_name(PLy_curr_procedure)); Py_DECREF(elem); @@ -2476,10 +2563,10 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) { char *sv = PyString_AsString(so); - plan->values[i] = - InputFunctionCall(&(plan->args[i].out.d.typfunc), + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), sv, - plan->args[i].out.d.typioparam, + plan->args[j].out.d.typioparam, -1); } PG_CATCH(); @@ -2490,17 +2577,17 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) PG_END_TRY(); Py_DECREF(so); - nulls[i] = ' '; + nulls[j] = ' '; } else { Py_DECREF(elem); - plan->values[i] = - InputFunctionCall(&(plan->args[i].out.d.typfunc), + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), NULL, - plan->args[i].out.d.typioparam, + plan->args[j].out.d.typioparam, -1); - nulls[i] = 'n'; + nulls[j] = 'n'; } } @@ -2511,6 +2598,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) } PG_CATCH(); { + int k; + MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); @@ -2518,21 +2607,21 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, long 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] != PointerGetDatum(NULL))) + if (!plan->args[k].out.d.typbyval && + (plan->values[k] != PointerGetDatum(NULL))) { - pfree(DatumGetPointer(plan->values[i])); - plan->values[i] = PointerGetDatum(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; } @@ -2576,10 +2665,10 @@ PLy_spi_execute_query(char *query, long 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; } @@ -2647,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; @@ -2678,6 +2767,8 @@ _PG_init(void) if (inited) return; + pg_bindtextdomain(TEXTDOMAIN); + Py_Initialize(); PLy_init_interp(); PLy_init_plpy(); @@ -2697,7 +2788,7 @@ PLy_init_interp(void) mainmod = PyImport_AddModule("__main__"); if (mainmod == NULL || PyErr_Occurred()) - PLy_elog(ERROR, "could not import \"__main__\" module."); + PLy_elog(ERROR, "could not import \"__main__\" module"); Py_INCREF(mainmod); PLy_interp_globals = PyModule_GetDict(mainmod); PLy_interp_safe_globals = PyDict_New(); @@ -2720,9 +2811,9 @@ PLy_init_plpy(void) * initialize plpy module */ if (PyType_Ready(&PLy_PlanType) < 0) - elog(ERROR, "could not init PLy_PlanType"); + elog(ERROR, "could not initialize PLy_PlanType"); if (PyType_Ready(&PLy_ResultType) < 0) - elog(ERROR, "could not init PLy_ResultType"); + elog(ERROR, "could not initialize PLy_ResultType"); plpy = Py_InitModule("plpy", PLy_methods); plpy_dict = PyModule_GetDict(plpy); @@ -2744,7 +2835,7 @@ 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 @@ -2806,7 +2897,7 @@ PLy_output(volatile int level, PyObject * self, PyObject * args) 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; @@ -2856,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,...) @@ -2866,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); @@ -2879,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 * @@ -2919,8 +3039,8 @@ PLy_traceback(int *xlevel) PyObject *eob, *vob = NULL; char *vstr, - *estr, - *xstr = NULL; + *estr; + StringInfoData xstr; /* * get the current exception @@ -2943,7 +3063,7 @@ PLy_traceback(int *xlevel) if (v && ((vob = PyObject_Str(v)) != NULL)) vstr = PyString_AsString(vob); else - vstr = "Unknown"; + vstr = "unknown"; /* * I'm not sure what to do if eob is NULL here -- we can't call PLy_elog @@ -2951,8 +3071,9 @@ PLy_traceback(int *xlevel) * recursion. I'm not even sure if eob could be NULL here -- would an * Assert() be more appropriate? */ - estr = eob ? PyString_AsString(eob) : "Unknown Exception"; - xstr = PLy_printf("%s: %s", estr, vstr); + estr = eob ? PyString_AsString(eob) : "unrecognized exception"; + initStringInfo(&xstr); + appendStringInfo(&xstr, "%s: %s", estr, vstr); Py_DECREF(eob); Py_XDECREF(vob); @@ -2969,49 +3090,7 @@ PLy_traceback(int *xlevel) *xlevel = ERROR; Py_DECREF(e); - return xstr; -} - -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; + return xstr.data; } /* python module code */ @@ -3030,15 +3109,12 @@ 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; } static char *