/********************************************************************** * plpython.c - python as a procedural language for PostgreSQL * * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.88 2006/09/16 13:35:49 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 #else #include #endif #include "postgres.h" /* system stuff */ #include #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 "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" #include #include PG_MODULE_MAGIC; /* convert Postgresql Datum or tuple into a PyObject. * input to Python. Tuples are converted to dictionary * objects. */ typedef PyObject *(*PLyDatumToObFunc) (const char *); typedef struct PLyDatumToOb { PLyDatumToObFunc func; FmgrInfo typfunc; Oid typioparam; bool typbyval; } PLyDatumToOb; typedef struct PLyTupleToOb { PLyDatumToOb *atts; int natts; } PLyTupleToOb; typedef union PLyTypeInput { PLyDatumToOb d; PLyTupleToOb r; } PLyTypeInput; /* convert PyObject to a Postgresql Datum or tuple. * output from Python */ typedef struct PLyObToDatum { FmgrInfo typfunc; /* The type's input function */ Oid typoid; /* The OID of the type */ Oid typioparam; bool typbyval; } PLyObToDatum; typedef struct PLyObToTuple { PLyObToDatum *atts; int natts; } PLyObToTuple; typedef union PLyTypeOutput { PLyObToDatum d; PLyObToTuple r; } PLyTypeOutput; /* all we need to move Postgresql data to Python objects, * and vis versa */ typedef struct PLyTypeInfo { PLyTypeInput in; PLyTypeOutput out; int is_rowtype; /* * is_rowtype can be: -1 not known yet (initial state) 0 scalar datatype * 1 rowtype 2 rowtype, but I/O functions not set up yet */ } PLyTypeInfo; /* cached procedure data */ typedef struct PLyProcedure { char *proname; /* SQL name of procedure */ char *pyname; /* Python name of procedure */ TransactionId fn_xmin; CommandId fn_cmin; bool fn_readonly; PLyTypeInfo result; /* also used to store info for trigger tuple * type */ bool is_setof; /* true, if procedure returns result set */ PyObject *setof; /* contents of result set. */ char **argnames; /* Argument names */ PLyTypeInfo args[FUNC_MAX_ARGS]; int nargs; PyObject *code; /* compiled procedure code */ PyObject *statics; /* data saved across calls, local scope */ PyObject *globals; /* data saved across calls, global scope */ PyObject *me; /* PyCObject containing pointer to this * PLyProcedure */ } PLyProcedure; /* Python objects */ typedef struct PLyPlanObject { PyObject_HEAD void *plan; /* return of an SPI_saveplan */ int nargs; Oid *types; Datum *values; PLyTypeInfo *args; } PLyPlanObject; typedef struct PLyResultObject { PyObject_HEAD /* HeapTuple *tuples; */ PyObject *nrows; /* number of rows returned by query */ PyObject *rows; /* data rows, or None if no data returned */ PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */ } PLyResultObject; /* function declarations */ /* Two exported functions: first is the magic telling Postgresql * what function call interface it implements. Second is for * initialization of the interpreter during library load. */ Datum plpython_call_handler(PG_FUNCTION_ARGS); void _PG_init(void); PG_FUNCTION_INFO_V1(plpython_call_handler); /* most of the remaining of the declarations, all static */ /* 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_interp(void); static void PLy_init_plpy(void); /* call PyErr_SetString with a vprint interface */ static void PLy_exception_set(PyObject *, const char *,...) __attribute__((format(printf, 2, 3))); /* Get the innermost python procedure called from the backend */ static char *PLy_procedure_name(PLyProcedure *); /* some utility functions */ static void PLy_elog(int, const char *,...); 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 char *PLy_strdup(const char *); static void PLy_free(void *); /* sub handlers for functions and triggers */ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *); static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *); static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *); static void PLy_function_delete_args(PLyProcedure *); static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *, HeapTuple *); static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *, TriggerData *, HeapTuple); static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *); static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid); static PLyProcedure *PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, HeapTuple procTup, char *key); static void PLy_procedure_compile(PLyProcedure *, const char *); static char *PLy_procedure_munge_source(const char *, const char *); static void PLy_procedure_delete(PLyProcedure *); static void PLy_typeinfo_init(PLyTypeInfo *); static void PLy_typeinfo_dealloc(PLyTypeInfo *); static void PLy_output_datum_func(PLyTypeInfo *, HeapTuple); static void PLy_output_datum_func2(PLyObToDatum *, HeapTuple); static void PLy_input_datum_func(PLyTypeInfo *, Oid, HeapTuple); static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple); static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc); static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); /* conversion functions */ static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); static PyObject *PLyBool_FromString(const char *); static PyObject *PLyFloat_FromString(const char *); static PyObject *PLyInt_FromString(const char *); static PyObject *PLyLong_FromString(const char *); static PyObject *PLyString_FromString(const char *); static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *); static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *); static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *); /* * Currently active plpython function */ static PLyProcedure *PLy_curr_procedure = NULL; /* * When a callback from Python into PG incurs an error, we temporarily store * the error information here, and return NULL to the Python interpreter. * Any further callback attempts immediately fail, and when the Python * interpreter returns to the calling function, we re-throw the error (even if * Python thinks it trapped the error and doesn't return NULL). Eventually * this ought to be improved to let Python code really truly trap the error, * but that's more of a change from the pre-8.0 semantics than I have time for * now --- it will only be possible if the callback query is executed inside a * subtransaction. */ static ErrorData *PLy_error_in_progress = NULL; static PyObject *PLy_interp_globals = NULL; static PyObject *PLy_interp_safe_globals = NULL; static PyObject *PLy_procedure_cache = NULL; /* Python exceptions */ static PyObject *PLy_exc_error = NULL; static PyObject *PLy_exc_fatal = NULL; static PyObject *PLy_exc_spi_error = NULL; /* some globals for the python module */ static char PLy_plan_doc[] = { "Store a PostgreSQL plan" }; static char PLy_result_doc[] = { "Results of a PostgreSQL query" }; /* * the function definitions */ /* * This routine is a crock, and so is everyplace that calls it. The problem * is that the cached form of plpython functions/queries is allocated permanently * (mostly via malloc()) and never released until backend exit. Subsidiary * data structures such as fmgr info records therefore must live forever * as well. A better implementation would store all this stuff in a per- * function memory context that could be reclaimed at need. In the meantime, * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever * it might allocate, and whatever the eventual function might allocate using * fn_mcxt, will live forever too. */ static void perm_fmgr_info(Oid functionId, FmgrInfo *finfo) { fmgr_info_cxt(functionId, finfo, TopMemoryContext); } Datum plpython_call_handler(PG_FUNCTION_ARGS) { Datum retval; PLyProcedure *save_curr_proc; PLyProcedure *volatile proc = NULL; if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "could not connect to SPI manager"); save_curr_proc = PLy_curr_procedure; PG_TRY(); { if (CALLED_AS_TRIGGER(fcinfo)) { TriggerData *tdata = (TriggerData *) fcinfo->context; HeapTuple trv; proc = PLy_procedure_get(fcinfo, RelationGetRelid(tdata->tg_relation)); PLy_curr_procedure = proc; trv = PLy_trigger_handler(fcinfo, proc); retval = PointerGetDatum(trv); } else { proc = PLy_procedure_get(fcinfo, InvalidOid); PLy_curr_procedure = proc; retval = PLy_function_handler(fcinfo, proc); } } PG_CATCH(); { PLy_curr_procedure = save_curr_proc; if (proc) { /* note: Py_DECREF needs braces around it, as of 2003/08 */ Py_DECREF(proc->me); } PyErr_Clear(); PG_RE_THROW(); } PG_END_TRY(); PLy_curr_procedure = save_curr_proc; Py_DECREF(proc->me); return retval; } /* trigger and function sub handlers * * the python function is expected to return Py_None if the tuple is * acceptable and unmodified. Otherwise it should return a PyString * object who's value is SKIP, or MODIFY. SKIP means don't perform * this action. MODIFY means the tuple has been modified, so update * tuple and perform action. SKIP and MODIFY assume the trigger fires * BEFORE the event and is ROW level. postgres expects the function * to take no arguments and return an argument of type trigger. */ static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) { HeapTuple rv = NULL; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; PG_TRY(); { plargs = PLy_trigger_build_args(fcinfo, proc, &rv); plrv = PLy_procedure_call(proc, "TD", plargs); Assert(plrv != NULL); Assert(!PLy_error_in_progress); /* * Disconnect from SPI manager */ if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); /* * return of None means we're happy with the tuple */ if (plrv != Py_None) { char *srv; if (!PyString_Check(plrv)) ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None or a String."))); srv = PyString_AsString(plrv); if (pg_strcasecmp(srv, "SKIP") == 0) rv = NULL; else if (pg_strcasecmp(srv, "MODIFY") == 0) { TriggerData *tdata = (TriggerData *) fcinfo->context; if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) rv = PLy_modify_tuple(proc, plargs, tdata, rv); else elog(WARNING, "ignoring modified tuple in DELETE trigger"); } else if (pg_strcasecmp(srv, "OK") != 0) { /* * accept "OK" as an alternative to None; otherwise, * raise an error */ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); } } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(plargs); Py_DECREF(plrv); return rv; } static HeapTuple PLy_modify_tuple(PLyProcedure * proc, PyObject * pltd, TriggerData *tdata, HeapTuple otup) { PyObject *volatile plntup; PyObject *volatile plkeys; PyObject *volatile platt; PyObject *volatile plval; PyObject *volatile plstr; HeapTuple rtup; int natts, i, attn, atti; int *volatile modattrs; Datum *volatile modvalues; char *volatile modnulls; TupleDesc tupdesc; plntup = plkeys = platt = plval = plstr = NULL; modattrs = NULL; modvalues = NULL; modnulls = NULL; PG_TRY(); { if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) elog(ERROR, "TD[\"new\"] deleted, unable to modify tuple"); if (!PyDict_Check(plntup)) elog(ERROR, "TD[\"new\"] is not a dictionary object"); Py_INCREF(plntup); plkeys = PyDict_Keys(plntup); natts = PyList_Size(plkeys); modattrs = (int *) palloc(natts * sizeof(int)); modvalues = (Datum *) palloc(natts * sizeof(Datum)); modnulls = (char *) palloc(natts * sizeof(char)); tupdesc = tdata->tg_relation->rd_att; for (i = 0; i < natts; i++) { char *src; platt = PyList_GetItem(plkeys, i); if (!PyString_Check(platt)) elog(ERROR, "attribute name is not a string"); attn = SPI_fnumber(tupdesc, PyString_AsString(platt)); if (attn == SPI_ERROR_NOATTRIBUTE) elog(ERROR, "invalid attribute \"%s\" in tuple", PyString_AsString(platt)); atti = attn - 1; plval = PyDict_GetItem(plntup, platt); if (plval == NULL) elog(FATAL, "python interpreter is probably corrupted"); Py_INCREF(plval); modattrs[i] = attn; if (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, "function \"%s\" could not modify tuple", proc->proname); src = PyString_AsString(plstr); modvalues[i] = InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, src, proc->result.out.r.atts[atti].typioparam, tupdesc->attrs[atti]->atttypmod); modnulls[i] = ' '; Py_DECREF(plstr); plstr = NULL; } else { modvalues[i] = InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, NULL, proc->result.out.r.atts[atti].typioparam, tupdesc->attrs[atti]->atttypmod); modnulls[i] = 'n'; } Py_DECREF(plval); plval = NULL; } rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, modattrs, modvalues, modnulls); if (rtup == NULL) elog(ERROR, "SPI_modifytuple failed -- error %d", SPI_result); } PG_CATCH(); { Py_XDECREF(plntup); Py_XDECREF(plkeys); Py_XDECREF(plval); Py_XDECREF(plstr); if (modnulls) pfree(modnulls); if (modvalues) pfree(modvalues); if (modattrs) pfree(modattrs); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(plntup); Py_DECREF(plkeys); pfree(modattrs); pfree(modvalues); pfree(modnulls); return rtup; } static PyObject * PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple *rv) { TriggerData *tdata = (TriggerData *) fcinfo->context; PyObject *pltname, *pltevent, *pltwhen, *pltlevel, *pltrelid, *plttablename, *plttableschema; PyObject *pltargs, *pytnew, *pytold; PyObject *volatile pltdata = NULL; char *stroid; PG_TRY(); { pltdata = PyDict_New(); if (!pltdata) PLy_elog(ERROR, "could not build arguments for trigger procedure"); pltname = PyString_FromString(tdata->tg_trigger->tgname); PyDict_SetItemString(pltdata, "name", pltname); Py_DECREF(pltname); stroid = DatumGetCString(DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->tg_relation->rd_id))); pltrelid = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "relid", pltrelid); Py_DECREF(pltrelid); pfree(stroid); stroid = SPI_getrelname(tdata->tg_relation); plttablename = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_name", plttablename); Py_DECREF(plttablename); pfree(stroid); stroid = SPI_getnspname(tdata->tg_relation); plttableschema = PyString_FromString(stroid); PyDict_SetItemString(pltdata, "table_schema", plttableschema); Py_DECREF(plttableschema); pfree(stroid); if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) pltwhen = PyString_FromString("BEFORE"); else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) pltwhen = PyString_FromString("AFTER"); else { elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); pltwhen = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "when", pltwhen); Py_DECREF(pltwhen); if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) { pltlevel = PyString_FromString("ROW"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { pltevent = PyString_FromString("INSERT"); PyDict_SetItemString(pltdata, "old", Py_None); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) { pltevent = PyString_FromString("DELETE"); PyDict_SetItemString(pltdata, "new", Py_None); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_trigtuple; } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { pltevent = PyString_FromString("UPDATE"); pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_newtuple; } else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) { pltlevel = PyString_FromString("STATEMENT"); PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); PyDict_SetItemString(pltdata, "old", Py_None); PyDict_SetItemString(pltdata, "new", Py_None); *rv = NULL; if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) pltevent = PyString_FromString("INSERT"); else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) pltevent = PyString_FromString("DELETE"); else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) pltevent = PyString_FromString("UPDATE"); else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); pltevent = NULL; /* keep compiler quiet */ } PyDict_SetItemString(pltdata, "event", pltevent); Py_DECREF(pltevent); } else elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); if (tdata->tg_trigger->tgnargs) { /* * all strings... */ int i; PyObject *pltarg; pltargs = PyList_New(tdata->tg_trigger->tgnargs); for (i = 0; i < tdata->tg_trigger->tgnargs; i++) { pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); /* * stolen, don't Py_DECREF */ PyList_SetItem(pltargs, i, pltarg); } } else { Py_INCREF(Py_None); pltargs = Py_None; } PyDict_SetItemString(pltdata, "args", pltargs); Py_DECREF(pltargs); } PG_CATCH(); { Py_XDECREF(pltdata); PG_RE_THROW(); } PG_END_TRY(); return pltdata; } /* function handler and friends */ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) { Datum rv; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; PyObject *volatile plrv_so = NULL; char *plrv_sc; PG_TRY(); { if (!proc->is_setof || proc->setof == NULL) { /* Simple type returning function or first time for SETOF function */ plargs = PLy_function_build_args(fcinfo, proc); plrv = PLy_procedure_call(proc, "args", plargs); if (!proc->is_setof) /* SETOF function parameters will be deleted when last row is returned */ PLy_function_delete_args(proc); Assert(plrv != NULL); Assert(!PLy_error_in_progress); } /* * 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("only value per call is allowed"))); } 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 can not be iterated"), errdetail("SETOF must be returned as 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; } } /* * 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 (proc->result.out.d.typoid == VOIDOID) { 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."))); fcinfo->isnull = false; rv = (Datum) 0; } else if (plrv == Py_None) { fcinfo->isnull = true; if (proc->result.is_rowtype < 1) rv = InputFunctionCall(&proc->result.out.d.typfunc, NULL, proc->result.out.d.typioparam, -1); else /* Tuple as None */ rv = (Datum) NULL; } else if (proc->result.is_rowtype >= 1) { HeapTuple tuple = NULL; if (PySequence_Check(plrv)) /* composite type as sequence (tuple, list etc) */ tuple = PLySequence_ToTuple(&proc->result, plrv); else if (PyMapping_Check(plrv)) /* composite type as mapping (currently only dict) */ tuple = PLyMapping_ToTuple(&proc->result, plrv); else /* returned as smth, must provide method __getattr__(name) */ tuple = PLyObject_ToTuple(&proc->result, plrv); if (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, "function \"%s\" could not create return value", proc->proname); plrv_sc = PyString_AsString(plrv_so); rv = InputFunctionCall(&proc->result.out.d.typfunc, plrv_sc, proc->result.out.d.typioparam, -1); } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); Py_XDECREF(plrv_so); PG_RE_THROW(); } PG_END_TRY(); Py_XDECREF(plargs); Py_DECREF(plrv); Py_XDECREF(plrv_so); return rv; } static PyObject * PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs) { PyObject *rv; PyDict_SetItemString(proc->globals, kargs, vargs); rv = PyEval_EvalCode((PyCodeObject *) proc->code, proc->globals, proc->globals); /* * If there was an error in a PG callback, propagate that no matter what * Python claims about its success. */ if (PLy_error_in_progress) { ErrorData *edata = PLy_error_in_progress; PLy_error_in_progress = NULL; ReThrowError(edata); } if (rv == NULL || PyErr_Occurred()) { Py_XDECREF(rv); PLy_elog(ERROR, "function \"%s\" failed", proc->proname); } return rv; } static PyObject * PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) { PyObject *volatile arg = NULL; PyObject *volatile args = NULL; int i; PG_TRY(); { args = PyList_New(proc->nargs); for (i = 0; i < proc->nargs; i++) { if (proc->args[i].is_rowtype > 0) { if (fcinfo->argnull[i]) arg = NULL; else { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup; td = DatumGetHeapTupleHeader(fcinfo->arg[i]); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Set up I/O funcs if not done yet */ if (proc->args[i].is_rowtype != 1) PLy_input_tuple_funcs(&(proc->args[i]), tupdesc); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } } else { if (fcinfo->argnull[i]) arg = NULL; else { char *ct; ct = OutputFunctionCall(&(proc->args[i].in.d.typfunc), fcinfo->arg[i]); arg = (proc->args[i].in.d.func) (ct); pfree(ct); } } if (arg == NULL) { Py_INCREF(Py_None); 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); arg = NULL; } } PG_CATCH(); { Py_XDECREF(arg); Py_XDECREF(args); PG_RE_THROW(); } PG_END_TRY(); return args; } static void PLy_function_delete_args(PLyProcedure *proc) { int i; if (!proc->argnames) return; for (i = 0; i < proc->nargs; i++) PyDict_DelItemString(proc->globals, proc->argnames[i]); } /* * PLyProcedure functions */ /* PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and * returns a new PLyProcedure. fcinfo is the call info, tgreloid is the * relation OID when calling a trigger, or InvalidOid (zero) for ordinary * function calls. */ static PLyProcedure * PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) { Oid fn_oid; HeapTuple procTup; char key[128]; PyObject *plproc; PLyProcedure *proc = NULL; int rv; fn_oid = fcinfo->flinfo->fn_oid; procTup = SearchSysCache(PROCOID, ObjectIdGetDatum(fn_oid), 0, 0, 0); if (!HeapTupleIsValid(procTup)) elog(ERROR, "cache lookup failed for function %u", fn_oid); rv = snprintf(key, sizeof(key), "%u_%u", fn_oid, tgreloid); if (rv >= sizeof(key) || rv < 0) elog(ERROR, "key too long"); plproc = PyDict_GetItemString(PLy_procedure_cache, key); if (plproc != NULL) { Py_INCREF(plproc); if (!PyCObject_Check(plproc)) elog(FATAL, "expected a PyCObject, didn't get one"); proc = PyCObject_AsVoidPtr(plproc); if (proc->me != plproc) elog(FATAL, "proc->me != plproc"); /* did we find an up-to-date cache entry? */ if (proc->fn_xmin != HeapTupleHeaderGetXmin(procTup->t_data) || proc->fn_cmin != HeapTupleHeaderGetCmin(procTup->t_data)) { Py_DECREF(plproc); proc = NULL; } } if (proc == NULL) proc = PLy_procedure_create(fcinfo, tgreloid, procTup, key); ReleaseSysCache(procTup); return proc; } static PLyProcedure * PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, HeapTuple procTup, char *key) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure *volatile proc; char *volatile procSource = NULL; Datum prosrcdatum; bool isnull; int i, rv; Datum argnames; Datum *elems; int nelems; procStruct = (Form_pg_proc) GETSTRUCT(procTup); if (OidIsValid(tgreloid)) rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u_trigger_%u", NameStr(procStruct->proname), fcinfo->flinfo->fn_oid, tgreloid); else rv = snprintf(procName, sizeof(procName), "__plpython_procedure_%s_%u", NameStr(procStruct->proname), fcinfo->flinfo->fn_oid); if (rv >= sizeof(procName) || rv < 0) elog(ERROR, "procedure name would overrun buffer"); proc = PLy_malloc(sizeof(PLyProcedure)); proc->proname = PLy_strdup(NameStr(procStruct->proname)); proc->pyname = PLy_strdup(procName); proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); /* Remember if function is STABLE/IMMUTABLE */ proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); PLy_typeinfo_init(&proc->result); for (i = 0; i < FUNC_MAX_ARGS; i++) PLy_typeinfo_init(&proc->args[i]); proc->nargs = 0; proc->code = proc->statics = NULL; proc->globals = proc->me = NULL; proc->is_setof = procStruct->proretset; proc->setof = NULL; proc->argnames = NULL; PG_TRY(); { /* * get information required for output conversion of the return value, * but only if this isn't a trigger. */ if (!CALLED_AS_TRIGGER(fcinfo)) { HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; rvTypeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(procStruct->prorettype), 0, 0, 0); if (!HeapTupleIsValid(rvTypeTup)) elog(ERROR, "cache lookup failed for type %u", procStruct->prorettype); rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); /* Disallow pseudotype result, except for void */ if (rvTypeStruct->typtype == 'p' && procStruct->prorettype != VOIDOID) { if (procStruct->prorettype == TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions may 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)))); } if (rvTypeStruct->typtype == 'c') { /* 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 procedure's * arguments. */ proc->nargs = fcinfo->nargs; if (proc->nargs) { argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull); if (!isnull) { 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); } } 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); /* 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.values[i])))); if (argTypeStruct->typtype != 'c') 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 */ ReleaseSysCache(argTypeTup); /* Fetch argument name */ if (proc->argnames) proc->argnames[i] = PLy_strdup(DatumGetCString(DirectFunctionCall1(textout, elems[i]))); } /* * get the text of the function. */ prosrcdatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); procSource = DatumGetCString(DirectFunctionCall1(textout, prosrcdatum)); PLy_procedure_compile(proc, procSource); pfree(procSource); proc->me = PyCObject_FromVoidPtr(proc, NULL); PyDict_SetItemString(PLy_procedure_cache, key, proc->me); } PG_CATCH(); { PLy_procedure_delete(proc); if (procSource) pfree(procSource); PG_RE_THROW(); } PG_END_TRY(); return proc; } static void PLy_procedure_compile(PLyProcedure * proc, const char *src) { PyObject *crv = NULL; char *msrc; proc->globals = PyDict_Copy(PLy_interp_globals); /* * SD is private preserved data between calls. GD is global data * shared by all functions */ proc->statics = PyDict_New(); PyDict_SetItemString(proc->globals, "SD", proc->statics); /* * insert the function code into the interpreter */ msrc = PLy_procedure_munge_source(proc->pyname, src); crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); free(msrc); if (crv != NULL && (!PyErr_Occurred())) { int clen; char call[NAMEDATALEN + 256]; Py_DECREF(crv); /* * compile a call to the function */ clen = snprintf(call, sizeof(call), "%s()", proc->pyname); if (clen < 0 || clen >= sizeof(call)) elog(ERROR, "string would overflow buffer"); proc->code = Py_CompileString(call, "", Py_eval_input); if (proc->code != NULL && (!PyErr_Occurred())) return; } else Py_XDECREF(crv); PLy_elog(ERROR, "could not compile function \"%s\"", proc->proname); } static char * PLy_procedure_munge_source(const char *name, const char *src) { char *mrc, *mp; const char *sp; size_t mlen, plen; /* * room for function source and the def statement */ mlen = (strlen(src) * 2) + strlen(name) + 16; mrc = PLy_malloc(mlen); plen = snprintf(mrc, mlen, "def %s():\n\t", name); Assert(plen >= 0 && plen < mlen); sp = src; mp = mrc + plen; while (*sp != '\0') { if (*sp == '\r' && *(sp + 1) == '\n') sp++; if (*sp == '\n' || *sp == '\r') { *mp++ = '\n'; *mp++ = '\t'; sp++; } else *mp++ = *sp++; } *mp++ = '\n'; *mp++ = '\n'; *mp = '\0'; if (mp > (mrc + mlen)) elog(FATAL, "buffer overrun in PLy_munge_source"); return mrc; } static void PLy_procedure_delete(PLyProcedure * proc) { int i; Py_XDECREF(proc->code); Py_XDECREF(proc->statics); Py_XDECREF(proc->globals); Py_XDECREF(proc->me); if (proc->proname) PLy_free(proc->proname); if (proc->pyname) PLy_free(proc->pyname); for (i = 0; i < proc->nargs; i++) { if (proc->args[i].is_rowtype == 1) { if (proc->args[i].in.r.atts) PLy_free(proc->args[i].in.r.atts); if (proc->args[i].out.r.atts) PLy_free(proc->args[i].out.r.atts); } 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. */ static void PLy_input_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) { int i; 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)); for (i = 0; i < desc->natts; i++) { HeapTuple typeTup; if (desc->attrs[i]->attisdropped) continue; typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_input_datum_func2(&(arg->in.r.atts[i]), desc->attrs[i]->atttypid, typeTup); ReleaseSysCache(typeTup); } } static void PLy_output_tuple_funcs(PLyTypeInfo * arg, TupleDesc desc) { int i; 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)); for (i = 0; i < desc->natts; i++) { HeapTuple typeTup; if (desc->attrs[i]->attisdropped) continue; typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum(desc->attrs[i]->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", desc->attrs[i]->atttypid); PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); ReleaseSysCache(typeTup); } } static void PLy_output_datum_func(PLyTypeInfo * arg, HeapTuple typeTup) { if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); arg->is_rowtype = 0; PLy_output_datum_func2(&(arg->out.d), typeTup); } static void 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; } static void PLy_input_datum_func(PLyTypeInfo * arg, Oid typeOid, HeapTuple typeTup) { if (arg->is_rowtype > 0) elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); arg->is_rowtype = 0; PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); } static void PLy_input_datum_func2(PLyDatumToOb * arg, Oid typeOid, HeapTuple typeTup) { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; /* Determine which kind of Python object we will convert to */ switch (typeOid) { case BOOLOID: arg->func = PLyBool_FromString; break; case FLOAT4OID: case FLOAT8OID: case NUMERICOID: arg->func = PLyFloat_FromString; break; case INT2OID: case INT4OID: arg->func = PLyInt_FromString; break; case INT8OID: arg->func = PLyLong_FromString; break; default: arg->func = PLyString_FromString; break; } } static void PLy_typeinfo_init(PLyTypeInfo * arg) { arg->is_rowtype = -1; arg->in.r.natts = arg->out.r.natts = 0; arg->in.r.atts = NULL; arg->out.r.atts = NULL; } static void PLy_typeinfo_dealloc(PLyTypeInfo * arg) { if (arg->is_rowtype == 1) { if (arg->in.r.atts) PLy_free(arg->in.r.atts); if (arg->out.r.atts) PLy_free(arg->out.r.atts); } } /* assumes that a bool is always returned as a 't' or 'f' */ static PyObject * PLyBool_FromString(const char *src) { if (src[0] == 't') return PyInt_FromLong(1); return PyInt_FromLong(0); } static PyObject * PLyFloat_FromString(const char *src) { double v; char *eptr; errno = 0; v = strtod(src, &eptr); if (*eptr != '\0' || errno) return NULL; return PyFloat_FromDouble(v); } static PyObject * PLyInt_FromString(const char *src) { long v; char *eptr; errno = 0; v = strtol(src, &eptr, 0); if (*eptr != '\0' || errno) return NULL; return PyInt_FromLong(v); } static PyObject * PLyLong_FromString(const char *src) { return PyLong_FromString((char *) src, NULL, 0); } static PyObject * PLyString_FromString(const char *src) { return PyString_FromString(src); } static PyObject * PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) { PyObject *volatile dict; int i; if (info->is_rowtype != 1) elog(ERROR, "PLyTypeInfo structure describes a datum"); dict = PyDict_New(); if (dict == NULL) PLy_elog(ERROR, "could not create tuple dictionary"); PG_TRY(); { for (i = 0; i < info->in.r.natts; i++) { char *key, *vsrc; Datum vattr; bool is_null; PyObject *value; if (desc->attrs[i]->attisdropped) continue; key = NameStr(desc->attrs[i]->attname); vattr = heap_getattr(tuple, (i + 1), desc, &is_null); if (is_null || info->in.r.atts[i].func == NULL) PyDict_SetItemString(dict, key, Py_None); else { vsrc = OutputFunctionCall(&info->in.r.atts[i].typfunc, vattr); /* * no exceptions allowed */ value = info->in.r.atts[i].func(vsrc); pfree(vsrc); PyDict_SetItemString(dict, key, value); Py_DECREF(value); } } } PG_CATCH(); { Py_DECREF(dict); PG_RE_THROW(); } PG_END_TRY(); return dict; } static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping) { TupleDesc desc; HeapTuple tuple; Datum *values; char *nulls; 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(char)*desc->natts); for (i = 0; i < desc->natts; ++i) { char *key; PyObject *value, *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] = 'n'; } else if (value) { char *valuestr; so = PyObject_Str(value); if (so == NULL) PLy_elog(ERROR, "can't convert mapping type"); 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] = ' '; } 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"))); Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } tuple = heap_formtuple(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; char *nulls; 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("returned sequence's length must be same as tuple's length"))); 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(char)*desc->natts); for (i = 0; i < desc->natts; ++i) { PyObject *value, *so; value = so = NULL; PG_TRY(); { value = PySequence_GetItem(sequence, i); Assert(value); if (value == Py_None) { values[i] = (Datum) NULL; nulls[i] = 'n'; } else if (value) { char *valuestr; so = PyObject_Str(value); if (so == NULL) PLy_elog(ERROR, "can't convert sequence type"); 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] = ' '; } Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } tuple = heap_formtuple(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; char *nulls; 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(char)*desc->natts); for (i = 0; i < desc->natts; ++i) { char *key; PyObject *value, *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] = 'n'; } else if (value) { char *valuestr; so = PyObject_Str(value); if (so == NULL) PLy_elog(ERROR, "can't convert object type"); 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] = ' '; } 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"))); Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } PG_END_TRY(); } tuple = heap_formtuple(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 *); static PyObject *PLy_notice(PyObject *, PyObject *); static PyObject *PLy_warning(PyObject *, PyObject *); static PyObject *PLy_error(PyObject *, PyObject *); static PyObject *PLy_fatal(PyObject *, PyObject *); /* PLyPlanObject, PLyResultObject and SPI interface */ #define is_PLyPlanObject(x) ((x)->ob_type == &PLy_PlanType) static PyObject *PLy_plan_new(void); static void PLy_plan_dealloc(PyObject *); static PyObject *PLy_plan_getattr(PyObject *, char *); static PyObject *PLy_plan_status(PyObject *, PyObject *); static PyObject *PLy_result_new(void); static void PLy_result_dealloc(PyObject *); static PyObject *PLy_result_getattr(PyObject *, char *); 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 PyObject *PLy_spi_prepare(PyObject *, PyObject *); static PyObject *PLy_spi_execute(PyObject *, PyObject *); 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); static PyTypeObject PLy_PlanType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "PLyPlan", /* tp_name */ sizeof(PLyPlanObject), /* tp_size */ 0, /* tp_itemsize */ /* * methods */ (destructor) PLy_plan_dealloc, /* tp_dealloc */ 0, /* tp_print */ (getattrfunc) PLy_plan_getattr, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ 0, /* tp_xxx4 */ PLy_plan_doc, /* tp_doc */ }; static PyMethodDef PLy_plan_methods[] = { {"status", PLy_plan_status, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; static PySequenceMethods PLy_result_as_sequence = { (inquiry) PLy_result_length, /* sq_length */ (binaryfunc) 0, /* sq_concat */ (intargfunc) 0, /* sq_repeat */ (intargfunc) PLy_result_item, /* sq_item */ (intintargfunc) PLy_result_slice, /* sq_slice */ (intobjargproc) PLy_result_ass_item, /* sq_ass_item */ (intintobjargproc) PLy_result_ass_slice, /* sq_ass_slice */ }; static PyTypeObject PLy_ResultType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "PLyResult", /* tp_name */ sizeof(PLyResultObject), /* tp_size */ 0, /* tp_itemsize */ /* * methods */ (destructor) PLy_result_dealloc, /* tp_dealloc */ 0, /* tp_print */ (getattrfunc) PLy_result_getattr, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ &PLy_result_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ 0, /* tp_xxx4 */ PLy_result_doc, /* tp_doc */ }; static PyMethodDef PLy_result_methods[] = { {"nrows", PLy_result_nrows, METH_VARARGS, NULL}, {"status", PLy_result_status, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; static PyMethodDef PLy_methods[] = { /* * logging methods */ {"debug", PLy_debug, METH_VARARGS, NULL}, {"log", PLy_log, METH_VARARGS, NULL}, {"info", PLy_info, METH_VARARGS, NULL}, {"notice", PLy_notice, METH_VARARGS, NULL}, {"warning", PLy_warning, METH_VARARGS, NULL}, {"error", PLy_error, METH_VARARGS, NULL}, {"fatal", PLy_fatal, METH_VARARGS, NULL}, /* * create a stored plan */ {"prepare", PLy_spi_prepare, METH_VARARGS, NULL}, /* * execute a plan or query */ {"execute", PLy_spi_execute, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; /* plan object methods */ static PyObject * PLy_plan_new(void) { PLyPlanObject *ob; if ((ob = PyObject_NEW(PLyPlanObject, &PLy_PlanType)) == NULL) return NULL; ob->plan = NULL; ob->nargs = 0; ob->types = NULL; ob->args = NULL; return (PyObject *) ob; } static void PLy_plan_dealloc(PyObject * arg) { PLyPlanObject *ob = (PLyPlanObject *) arg; if (ob->plan) SPI_freeplan(ob->plan); if (ob->types) PLy_free(ob->types); if (ob->args) { int i; for (i = 0; i < ob->nargs; i++) PLy_typeinfo_dealloc(&ob->args[i]); PLy_free(ob->args); } PyMem_DEL(arg); } static PyObject * PLy_plan_getattr(PyObject * self, char *name) { return Py_FindMethod(PLy_plan_methods, self, name); } static PyObject * PLy_plan_status(PyObject * self, PyObject * args) { if (PyArg_ParseTuple(args, "")) { Py_INCREF(Py_True); return Py_True; /* return PyInt_FromLong(self->status); */ } PyErr_SetString(PLy_exc_error, "plan.status() takes no arguments"); return NULL; } /* result object methods */ static PyObject * PLy_result_new(void) { PLyResultObject *ob; if ((ob = PyObject_NEW(PLyResultObject, &PLy_ResultType)) == NULL) return NULL; /* ob->tuples = NULL; */ Py_INCREF(Py_None); ob->status = Py_None; ob->nrows = PyInt_FromLong(-1); ob->rows = PyList_New(0); return (PyObject *) ob; } static void PLy_result_dealloc(PyObject * arg) { PLyResultObject *ob = (PLyResultObject *) arg; Py_XDECREF(ob->nrows); Py_XDECREF(ob->rows); Py_XDECREF(ob->status); PyMem_DEL(ob); } static PyObject * PLy_result_getattr(PyObject * self, char *name) { return Py_FindMethod(PLy_result_methods, self, name); } static PyObject * PLy_result_nrows(PyObject * self, PyObject * args) { PLyResultObject *ob = (PLyResultObject *) self; Py_INCREF(ob->nrows); return ob->nrows; } static PyObject * PLy_result_status(PyObject * self, PyObject * args) { PLyResultObject *ob = (PLyResultObject *) self; Py_INCREF(ob->status); return ob->status; } static int PLy_result_length(PyObject * arg) { PLyResultObject *ob = (PLyResultObject *) arg; return PyList_Size(ob->rows); } static PyObject * PLy_result_item(PyObject * arg, int idx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; rv = PyList_GetItem(ob->rows, idx); if (rv != NULL) Py_INCREF(rv); return rv; } static int PLy_result_ass_item(PyObject * arg, int idx, PyObject * item) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; Py_INCREF(item); rv = PyList_SetItem(ob->rows, idx, item); return rv; } static PyObject * PLy_result_slice(PyObject * arg, int lidx, int hidx) { PyObject *rv; PLyResultObject *ob = (PLyResultObject *) arg; rv = PyList_GetSlice(ob->rows, lidx, hidx); if (rv == NULL) return NULL; Py_INCREF(rv); return rv; } static int PLy_result_ass_slice(PyObject * arg, int lidx, int hidx, PyObject * slice) { int rv; PLyResultObject *ob = (PLyResultObject *) arg; rv = PyList_SetSlice(ob->rows, lidx, hidx, slice); return rv; } /* SPI interface */ static PyObject * PLy_spi_prepare(PyObject * self, PyObject * args) { PLyPlanObject *plan; PyObject *list = NULL; PyObject *volatile optr = NULL; char *query; void *tmpplan; MemoryContext oldcontext; /* Can't execute more if we have an unhandled error */ if (PLy_error_in_progress) { PyErr_SetString(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()"); return NULL; } if (list && (!PySequence_Check(list))) { PyErr_SetString(PLy_exc_spi_error, "Second argument in plpy.prepare() must be a sequence"); return NULL; } if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) return NULL; oldcontext = CurrentMemoryContext; PG_TRY(); { if (list != NULL) { int nargs, i; nargs = PySequence_Length(list); if (nargs > 0) { plan->nargs = nargs; plan->types = PLy_malloc(sizeof(Oid) * nargs); plan->values = PLy_malloc(sizeof(Datum) * nargs); plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs); /* * the other loop might throw an exception, if PLyTypeInfo * member isn't properly initialized the Py_DECREF(plan) will * go boom */ for (i = 0; i < nargs; i++) { PLy_typeinfo_init(&plan->args[i]); plan->values[i] = PointerGetDatum(NULL); } for (i = 0; i < nargs; i++) { char *sptr; List *names; HeapTuple typeTup; Form_pg_type typeStruct; optr = PySequence_GetItem(list, i); if (!PyString_Check(optr)) elog(ERROR, "Type names must be strings."); sptr = PyString_AsString(optr); /* * Parse possibly-qualified type name and look it up in * pg_type */ names = stringToQualifiedNameList(sptr, "PLy_spi_prepare"); typeTup = typenameType(NULL, makeTypeNameFromNameList(names)); Py_DECREF(optr); optr = NULL; /* this is important */ plan->types[i] = HeapTupleGetOid(typeTup); typeStruct = (Form_pg_type) GETSTRUCT(typeTup); if (typeStruct->typtype != 'c') PLy_output_datum_func(&plan->args[i], typeTup); else elog(ERROR, "tuples not handled in plpy.prepare, yet."); ReleaseSysCache(typeTup); } } } plan->plan = SPI_prepare(query, plan->nargs, plan->types); if (plan->plan == NULL) elog(ERROR, "SPI_prepare failed: %s", SPI_result_code_string(SPI_result)); /* transfer plan from procCxt to topCxt */ tmpplan = plan->plan; plan->plan = SPI_saveplan(tmpplan); SPI_freeplan(tmpplan); if (plan->plan == NULL) elog(ERROR, "SPI_saveplan failed: %s", SPI_result_code_string(SPI_result)); } PG_CATCH(); { MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); Py_DECREF(plan); Py_XDECREF(optr); if (!PyErr_Occurred()) PyErr_SetString(PLy_exc_spi_error, "Unknown error in PLy_spi_prepare"); /* XXX this oughta be replaced with errcontext mechanism */ PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_curr_procedure)); return NULL; } PG_END_TRY(); return (PyObject *) plan; } /* execute(query="select * from foo", limit=5) * execute(plan=plan, values=(foo, bar), limit=5) */ static PyObject * PLy_spi_execute(PyObject * self, PyObject * args) { char *query; PyObject *plan; PyObject *list = NULL; 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."); return NULL; } if (PyArg_ParseTuple(args, "s|l", &query, &limit)) return PLy_spi_execute_query(query, limit); PyErr_Clear(); 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."); return NULL; } static PyObject * PLy_spi_execute_plan(PyObject * ob, PyObject * list, long limit) { volatile int nargs; int i, rv; PLyPlanObject *plan; MemoryContext oldcontext; if (list != NULL) { if (!PySequence_Check(list) || PyString_Check(list)) { char *msg = "plpy.execute() takes a sequence as its second argument"; PyErr_SetString(PLy_exc_spi_error, msg); return NULL; } nargs = PySequence_Length(list); } else nargs = 0; plan = (PLyPlanObject *) ob; if (nargs != plan->nargs) { char *sv; PyObject *so = PyObject_Str(list); if (!so) PLy_elog(ERROR, "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); Py_DECREF(so); return NULL; } oldcontext = CurrentMemoryContext; PG_TRY(); { char *nulls = palloc(nargs * sizeof(char)); for (i = 0; i < nargs; i++) { PyObject *elem, *so; elem = PySequence_GetItem(list, i); if (elem != Py_None) { so = PyObject_Str(elem); if (!so) PLy_elog(ERROR, "function \"%s\" could not execute plan", PLy_procedure_name(PLy_curr_procedure)); Py_DECREF(elem); PG_TRY(); { char *sv = PyString_AsString(so); plan->values[i] = InputFunctionCall(&(plan->args[i].out.d.typfunc), sv, plan->args[i].out.d.typioparam, -1); } PG_CATCH(); { Py_DECREF(so); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(so); nulls[i] = ' '; } else { Py_DECREF(elem); plan->values[i] = InputFunctionCall(&(plan->args[i].out.d.typfunc), NULL, plan->args[i].out.d.typioparam, -1); nulls[i] = 'n'; } } rv = SPI_execute_plan(plan->plan, plan->values, nulls, PLy_curr_procedure->fn_readonly, limit); pfree(nulls); } PG_CATCH(); { MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); /* * cleanup plan->values array */ for (i = 0; i < nargs; i++) { if (!plan->args[i].out.d.typbyval && (plan->values[i] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[i])); plan->values[i] = PointerGetDatum(NULL); } } if (!PyErr_Occurred()) PyErr_SetString(PLy_exc_error, "Unknown error in PLy_spi_execute_plan"); /* XXX this oughta be replaced with errcontext mechanism */ PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_curr_procedure)); return NULL; } PG_END_TRY(); for (i = 0; i < nargs; i++) { if (!plan->args[i].out.d.typbyval && (plan->values[i] != PointerGetDatum(NULL))) { pfree(DatumGetPointer(plan->values[i])); plan->values[i] = PointerGetDatum(NULL); } } if (rv < 0) { PLy_exception_set(PLy_exc_spi_error, "SPI_execute_plan failed: %s", SPI_result_code_string(rv)); return NULL; } return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); } static PyObject * PLy_spi_execute_query(char *query, long limit) { int rv; MemoryContext oldcontext; oldcontext = CurrentMemoryContext; PG_TRY(); { rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit); } PG_CATCH(); { MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); if (!PyErr_Occurred()) PyErr_SetString(PLy_exc_spi_error, "Unknown error in PLy_spi_execute_query"); /* XXX this oughta be replaced with errcontext mechanism */ PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_curr_procedure)); return NULL; } PG_END_TRY(); if (rv < 0) { PLy_exception_set(PLy_exc_spi_error, "SPI_execute failed: %s", SPI_result_code_string(rv)); return NULL; } return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); } static PyObject * PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) { PLyResultObject *result; MemoryContext oldcontext; result = (PLyResultObject *) PLy_result_new(); Py_DECREF(result->status); result->status = PyInt_FromLong(status); if (status > 0 && tuptable == NULL) { Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); } else if (status > 0 && tuptable != NULL) { PLyTypeInfo args; int i; Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); PLy_typeinfo_init(&args); oldcontext = CurrentMemoryContext; PG_TRY(); { if (rows) { Py_DECREF(result->rows); result->rows = PyList_New(rows); PLy_input_tuple_funcs(&args, tuptable->tupdesc); for (i = 0; i < rows; i++) { PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], tuptable->tupdesc); PyList_SetItem(result->rows, i, row); } PLy_typeinfo_dealloc(&args); SPI_freetuptable(tuptable); } } PG_CATCH(); { MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); if (!PyErr_Occurred()) PyErr_SetString(PLy_exc_error, "Unknown error in PLy_spi_execute_fetch_result"); Py_DECREF(result); PLy_typeinfo_dealloc(&args); return NULL; } PG_END_TRY(); } return (PyObject *) result; } /* * language handler and interpreter initialization */ /* * _PG_init() - library load-time initialization * * DO NOT make this static nor change its name! */ void _PG_init(void) { /* Be sure we do initialization only once (should be redundant now) */ static bool inited = false; if (inited) return; Py_Initialize(); PLy_init_interp(); PLy_init_plpy(); if (PyErr_Occurred()) PLy_elog(FATAL, "untrapped error in initialization"); PLy_procedure_cache = PyDict_New(); if (PLy_procedure_cache == NULL) PLy_elog(ERROR, "could not create procedure cache"); inited = true; } static void PLy_init_interp(void) { PyObject *mainmod; mainmod = PyImport_AddModule("__main__"); if (mainmod == NULL || PyErr_Occurred()) PLy_elog(ERROR, "could not import \"__main__\" module."); Py_INCREF(mainmod); PLy_interp_globals = PyModule_GetDict(mainmod); PLy_interp_safe_globals = PyDict_New(); PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals); Py_DECREF(mainmod); if (PLy_interp_globals == NULL || PyErr_Occurred()) PLy_elog(ERROR, "could not initialize globals"); } static void PLy_init_plpy(void) { PyObject *main_mod, *main_dict, *plpy_mod; PyObject *plpy, *plpy_dict; /* * initialize plpy module */ PLy_PlanType.ob_type = PLy_ResultType.ob_type = &PyType_Type; plpy = Py_InitModule("plpy", PLy_methods); plpy_dict = PyModule_GetDict(plpy); /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */ PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL); PyDict_SetItemString(plpy_dict, "Error", PLy_exc_error); PyDict_SetItemString(plpy_dict, "Fatal", PLy_exc_fatal); PyDict_SetItemString(plpy_dict, "SPIError", PLy_exc_spi_error); /* * initialize main module, and add plpy */ main_mod = PyImport_AddModule("__main__"); main_dict = PyModule_GetDict(main_mod); plpy_mod = PyImport_AddModule("plpy"); PyDict_SetItemString(main_dict, "plpy", plpy_mod); if (PyErr_Occurred()) elog(ERROR, "could not init plpy"); } /* the python interface to the elog function * don't confuse these with PLy_elog */ static PyObject *PLy_output(volatile int, PyObject *, PyObject *); static PyObject * PLy_debug(PyObject * self, PyObject * args) { return PLy_output(DEBUG2, self, args); } static PyObject * PLy_log(PyObject * self, PyObject * args) { return PLy_output(LOG, self, args); } static PyObject * PLy_info(PyObject * self, PyObject * args) { return PLy_output(INFO, self, args); } static PyObject * PLy_notice(PyObject * self, PyObject * args) { return PLy_output(NOTICE, self, args); } static PyObject * PLy_warning(PyObject * self, PyObject * args) { return PLy_output(WARNING, self, args); } static PyObject * PLy_error(PyObject * self, PyObject * args) { return PLy_output(ERROR, self, args); } static PyObject * PLy_fatal(PyObject * self, PyObject * args) { return PLy_output(FATAL, self, args); } static PyObject * PLy_output(volatile int level, PyObject * self, PyObject * args) { PyObject *so; char *volatile sv; MemoryContext oldcontext; so = PyObject_Str(args); if (so == NULL || ((sv = PyString_AsString(so)) == NULL)) { level = ERROR; sv = "Unable to parse error message in `plpy.elog'"; } oldcontext = CurrentMemoryContext; PG_TRY(); { elog(level, "%s", sv); } PG_CATCH(); { MemoryContextSwitchTo(oldcontext); PLy_error_in_progress = CopyErrorData(); FlushErrorState(); Py_XDECREF(so); /* * returning NULL here causes the python interpreter to bail. when * control passes back to PLy_procedure_call, we check for PG * exceptions and re-throw the error. */ PyErr_SetString(PLy_exc_error, sv); return NULL; } PG_END_TRY(); Py_XDECREF(so); /* * return a legal object so the interpreter will continue on its merry way */ Py_INCREF(Py_None); return Py_None; } /* * 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 the SQL name, not the internal Python procedure name */ static char * PLy_procedure_name(PLyProcedure * proc) { if (proc == NULL) return ""; return proc->proname; } /* output a python traceback/exception via the postgresql elog * function. not pretty. */ static void PLy_exception_set(PyObject * exc, const char *fmt,...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); PyErr_SetString(exc, buf); } /* Emit a PG error or notice, together with any available info about the * current Python error. This should be used to propagate Python errors * into PG. */ static void PLy_elog(int elevel, const char *fmt,...) { va_list ap; char *xmsg, *emsg; int xlevel; xmsg = PLy_traceback(&xlevel); va_start(ap, fmt); emsg = PLy_vprintf(fmt, ap); va_end(ap); PG_TRY(); { ereport(elevel, (errmsg("plpython: %s", emsg), (xmsg) ? errdetail("%s", xmsg) : 0)); } PG_CATCH(); { PLy_free(emsg); if (xmsg) PLy_free(xmsg); PG_RE_THROW(); } PG_END_TRY(); PLy_free(emsg); if (xmsg) PLy_free(xmsg); } static char * PLy_traceback(int *xlevel) { PyObject *e, *v, *tb; PyObject *eob, *vob = NULL; char *vstr, *estr, *xstr = NULL; /* * get the current exception */ PyErr_Fetch(&e, &v, &tb); /* * oops, no exception, return */ if (e == NULL) { *xlevel = WARNING; return NULL; } PyErr_NormalizeException(&e, &v, &tb); Py_XDECREF(tb); eob = PyObject_Str(e); if (v && ((vob = PyObject_Str(v)) != NULL)) vstr = PyString_AsString(vob); else vstr = "Unknown"; /* * 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) : "Unknown Exception"; xstr = PLy_printf("%s: %s", estr, vstr); Py_DECREF(eob); Py_XDECREF(vob); Py_XDECREF(v); /* * intuit an appropriate error level based on the exception type */ if (PLy_exc_error && PyErr_GivenExceptionMatches(e, PLy_exc_error)) *xlevel = ERROR; else if (PLy_exc_fatal && PyErr_GivenExceptionMatches(e, PLy_exc_fatal)) *xlevel = FATAL; else *xlevel = ERROR; 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; } /* python module code */ /* some dumb utility functions */ static void * PLy_malloc(size_t bytes) { void *ptr = malloc(bytes); if (ptr == NULL) ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); return ptr; } static void * PLy_realloc(void *optr, size_t bytes) { void *nptr = realloc(optr, bytes); if (nptr == NULL) ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); return nptr; } static char * PLy_strdup(const char *str) { char *result; size_t len; len = strlen(str) + 1; result = PLy_malloc(len); memcpy(result, str, len); return result; } /* define this away */ static void PLy_free(void *ptr) { free(ptr); }