static HeapTuple PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, HeapTuple otup) { PyObject *volatile plntup; PyObject *volatile plkeys; PyObject *volatile plval; HeapTuple rtup; int natts, i, attn, atti; int *volatile modattrs; Datum *volatile modvalues; char *volatile modnulls; TupleDesc tupdesc; ErrorContextCallback plerrcontext; plerrcontext.callback = plpython_trigger_error_callback; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; plntup = plkeys = plval = NULL; modattrs = NULL; modvalues = NULL; modnulls = NULL; PG_TRY(); { if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("TD[\"new\"] deleted, cannot modify row"))); Py_INCREF(plntup); if (!PyDict_Check(plntup)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("TD[\"new\"] is not a dictionary"))); 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++) { PyObject *platt; char *plattstr; platt = PyList_GetItem(plkeys, i); if (PyString_Check(platt)) plattstr = PyString_AsString(platt); else if (PyUnicode_Check(platt)) plattstr = PLyUnicode_AsString(platt); else { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i))); plattstr = NULL; /* keep compiler quiet */ } attn = SPI_fnumber(tupdesc, plattstr); if (attn == SPI_ERROR_NOATTRIBUTE) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", plattstr))); 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) { PLyObToDatum *att = &proc->result.out.r.atts[atti]; modvalues[i] = (att->func) (att, tupdesc->attrs[atti]->atttypmod, plval); modnulls[i] = ' '; } 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); 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); error_context_stack = plerrcontext.previous; return rtup; }
/* prepare(query="select * from foo") * prepare(query="select * from foo where bar = $1", params=["text"]) * prepare(query="select * from foo where bar = $1", params=["text"], limit=5) */ PyObject * PLy_spi_prepare(PyObject *self, PyObject *args) { PLyPlanObject *plan; PyObject *list = NULL; PyObject *volatile optr = NULL; char *query; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; volatile int nargs; if (!PyArg_ParseTuple(args, "s|O", &query, &list)) return NULL; if (list && (!PySequence_Check(list))) { PLy_exception_set(PyExc_TypeError, "second argument of plpy.prepare must be a sequence"); return NULL; } if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) return NULL; nargs = list ? PySequence_Length(list) : 0; plan->nargs = nargs; plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL; plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL; plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL; oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { int i; /* * the other loop might throw an exception, if PLyTypeInfo member * isn't properly initialized the Py_DECREF(plan) will go boom */ for (i = 0; i < nargs; i++) { PLy_typeinfo_init(&plan->args[i]); plan->values[i] = PointerGetDatum(NULL); } for (i = 0; i < nargs; i++) { char *sptr; HeapTuple typeTup; Oid typeId; int32 typmod; Form_pg_type typeStruct; optr = PySequence_GetItem(list, i); if (PyString_Check(optr)) sptr = PyString_AsString(optr); else if (PyUnicode_Check(optr)) sptr = PLyUnicode_AsString(optr); else { ereport(ERROR, (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); sptr = NULL; /* keep compiler quiet */ } /******************************************************** * 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 = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeId)); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", typeId); Py_DECREF(optr); /* * set optr to NULL, so we won't try to unref it again in case of * an error */ optr = NULL; plan->types[i] = typeId; typeStruct = (Form_pg_type) GETSTRUCT(typeTup); if (typeStruct->typtype != TYPTYPE_COMPOSITE) PLy_output_datum_func(&plan->args[i], typeTup); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpy.prepare does not support composite types"))); ReleaseSysCache(typeTup); } pg_verifymbstr(query, strlen(query), false); 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 */ if (SPI_keepplan(plan->plan)) elog(ERROR, "SPI_keepplan failed"); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { Py_DECREF(plan); Py_XDECREF(optr); PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); Assert(plan->plan != NULL); return (PyObject *) plan; }
/* trigger subhandler * * the python function is expected to return Py_None if the tuple is * acceptable and unmodified. Otherwise it should return a PyString * object who's value is SKIP, or MODIFY. SKIP means don't perform * this action. MODIFY means the tuple has been modified, so update * tuple and perform action. SKIP and MODIFY assume the trigger fires * BEFORE the event and is ROW level. postgres expects the function * to take no arguments and return an argument of type trigger. */ HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) { HeapTuple rv = NULL; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; TriggerData *tdata; Assert(CALLED_AS_TRIGGER(fcinfo)); /* * 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. */ 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); PG_TRY(); { plargs = PLy_trigger_build_args(fcinfo, proc, &rv); plrv = PLy_procedure_call(proc, "TD", plargs); Assert(plrv != NULL); /* * 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)) srv = PyString_AsString(plrv); else if (PyUnicode_Check(plrv)) srv = PLyUnicode_AsString(plrv); else { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None or a string."))); srv = NULL; /* keep compiler quiet */ } 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 ereport(WARNING, (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); } else if (pg_strcasecmp(srv, "OK") != 0) { /* * accept "OK" as an alternative to None; otherwise, raise an * error */ ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("unexpected return value from trigger procedure"), errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); } } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); PG_RE_THROW(); } PG_END_TRY(); Py_DECREF(plargs); Py_DECREF(plrv); return rv; }