Commits

Peter Eisentraut committed 147c248

Split plpython.c into smaller pieces

This moves the code around from one huge file into hopefully logical
and more manageable modules. For the most part, the code itself was
not touched, except: PLy_function_handler and PLy_trigger_handler were
renamed to PLy_exec_function and PLy_exec_trigger, because they were
not actually handlers in the PL handler sense, and it makes the naming
more similar to the way PL/pgSQL is organized. The initialization of
the procedure caches was separated into a new function
init_procedure_caches to keep the hash tables private to
plpy_procedures.c.

Jan Urbański and Peter Eisentraut

Comments (0)

Files changed (28)

src/pl/plpython/Makefile

 
 NAME = plpython$(python_majorversion)
 
-OBJS = plpython.o
+OBJS = \
+	plpy_cursorobject.o \
+	plpy_elog.o \
+	plpy_exec.o \
+	plpy_main.o \
+	plpy_planobject.o \
+	plpy_plpymodule.o \
+	plpy_procedure.o \
+	plpy_resultobject.o \
+	plpy_spi.o \
+	plpy_subxactobject.o \
+	plpy_typeio.o \
+	plpy_util.o
 
 DATA = $(NAME)u.control $(NAME)u--1.0.sql $(NAME)u--unpackaged--1.0.sql
 ifeq ($(python_majorversion),2)
 # distprep and maintainer-clean rules should be run even if we can't build.
 
 # Force this dependency to be known even without dependency info built:
-plpython.o: spiexceptions.h
+plpython_plpy.o: spiexceptions.h
 
 spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl
 	$(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@

src/pl/plpython/nls.mk

 # src/pl/plpython/nls.mk
 CATALOG_NAME     = plpython
 AVAIL_LANGUAGES  = de es fr it ja pt_BR ro tr zh_CN zh_TW
-GETTEXT_FILES    = plpython.c
+GETTEXT_FILES    = plpy_cursorobject.c plpy_elog.c plpy_exec.c plpy_main.c plpy_planobject.c plpy_plpymodule.c \
+                   plpy_procedure.c plpy_resultobject.c plpy_spi.c plpy_subxactobject.c plpy_typeio.c plpy_util.c
 GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) PLy_elog:2 PLy_exception_set:2 PLy_exception_set_plural:2,3
 GETTEXT_FLAGS    = $(BACKEND_COMMON_GETTEXT_FLAGS) \
     PLy_elog:2:c-format \

src/pl/plpython/plpy_cursorobject.c

+/*
+ * the PLyCursor class
+ *
+ * src/pl/plpython/plpy_cursorobject.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "mb/pg_wchar.h"
+
+#include "plpython.h"
+
+#include "plpy_cursorobject.h"
+
+#include "plpy_elog.h"
+#include "plpy_planobject.h"
+#include "plpy_procedure.h"
+#include "plpy_resultobject.h"
+#include "plpy_spi.h"
+
+
+static PyObject *PLy_cursor_query(const char *);
+static PyObject *PLy_cursor_plan(PyObject *, PyObject *);
+static void PLy_cursor_dealloc(PyObject *);
+static PyObject *PLy_cursor_iternext(PyObject *);
+static PyObject *PLy_cursor_fetch(PyObject *, PyObject *);
+static PyObject *PLy_cursor_close(PyObject *, PyObject *);
+
+static char PLy_cursor_doc[] = {
+	"Wrapper around a PostgreSQL cursor"
+};
+
+static PyMethodDef PLy_cursor_methods[] = {
+	{"fetch", PLy_cursor_fetch, METH_VARARGS, NULL},
+	{"close", PLy_cursor_close, METH_NOARGS, NULL},
+	{NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_CursorType = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"PLyCursor",		/* tp_name */
+	sizeof(PLyCursorObject),	/* tp_size */
+	0,							/* tp_itemsize */
+
+	/*
+	 * methods
+	 */
+	PLy_cursor_dealloc,			/* tp_dealloc */
+	0,							/* tp_print */
+	0,							/* 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 */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER,	/* tp_flags */
+	PLy_cursor_doc,				/* tp_doc */
+	0,							/* tp_traverse */
+	0,							/* tp_clear */
+	0,							/* tp_richcompare */
+	0,							/* tp_weaklistoffset */
+	PyObject_SelfIter,			/* tp_iter */
+	PLy_cursor_iternext,		/* tp_iternext */
+	PLy_cursor_methods,			/* tp_tpmethods */
+};
+
+void
+PLy_cursor_init_type(void)
+{
+	if (PyType_Ready(&PLy_CursorType) < 0)
+		elog(ERROR, "could not initialize PLy_CursorType");
+}
+
+PyObject *
+PLy_cursor(PyObject *self, PyObject *args)
+{
+	char	   *query;
+	PyObject   *plan;
+	PyObject   *planargs = NULL;
+
+	if (PyArg_ParseTuple(args, "s", &query))
+		return PLy_cursor_query(query);
+
+	PyErr_Clear();
+
+	if (PyArg_ParseTuple(args, "O|O", &plan, &planargs))
+		return PLy_cursor_plan(plan, planargs);
+
+	PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan");
+	return NULL;
+}
+
+
+static PyObject *
+PLy_cursor_query(const char *query)
+{
+	PLyCursorObject	*cursor;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+
+	if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL)
+		return NULL;
+	cursor->portalname = NULL;
+	cursor->closed = false;
+	PLy_typeinfo_init(&cursor->result);
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		SPIPlanPtr	plan;
+		Portal		portal;
+
+		pg_verifymbstr(query, strlen(query), false);
+
+		plan = SPI_prepare(query, 0, NULL);
+		if (plan == NULL)
+			elog(ERROR, "SPI_prepare failed: %s",
+				 SPI_result_code_string(SPI_result));
+
+		portal = SPI_cursor_open(NULL, plan, NULL, NULL,
+								 PLy_curr_procedure->fn_readonly);
+		SPI_freeplan(plan);
+
+		if (portal == NULL)
+			elog(ERROR, "SPI_cursor_open() failed:%s",
+				 SPI_result_code_string(SPI_result));
+
+		cursor->portalname = PLy_strdup(portal->name);
+
+		PLy_spi_subtransaction_commit(oldcontext, oldowner);
+	}
+	PG_CATCH();
+	{
+		PLy_spi_subtransaction_abort(oldcontext, oldowner);
+		return NULL;
+	}
+	PG_END_TRY();
+
+	Assert(cursor->portalname != NULL);
+	return (PyObject *) cursor;
+}
+
+static PyObject *
+PLy_cursor_plan(PyObject *ob, PyObject *args)
+{
+	PLyCursorObject	*cursor;
+	volatile int nargs;
+	int			i;
+	PLyPlanObject *plan;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+
+	if (args)
+	{
+		if (!PySequence_Check(args) || PyString_Check(args) || PyUnicode_Check(args))
+		{
+			PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument");
+			return NULL;
+		}
+		nargs = PySequence_Length(args);
+	}
+	else
+		nargs = 0;
+
+	plan = (PLyPlanObject *) ob;
+
+	if (nargs != plan->nargs)
+	{
+		char	   *sv;
+		PyObject   *so = PyObject_Str(args);
+
+		if (!so)
+			PLy_elog(ERROR, "could not execute plan");
+		sv = PyString_AsString(so);
+		PLy_exception_set_plural(PyExc_TypeError,
+								 "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;
+	}
+
+	if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL)
+		return NULL;
+	cursor->portalname = NULL;
+	cursor->closed = false;
+	PLy_typeinfo_init(&cursor->result);
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		Portal		portal;
+		char	   *volatile nulls;
+		volatile int j;
+
+		if (nargs > 0)
+			nulls = palloc(nargs * sizeof(char));
+		else
+			nulls = NULL;
+
+		for (j = 0; j < nargs; j++)
+		{
+			PyObject   *elem;
+
+			elem = PySequence_GetItem(args, j);
+			if (elem != Py_None)
+			{
+				PG_TRY();
+				{
+					plan->values[j] =
+						plan->args[j].out.d.func(&(plan->args[j].out.d),
+												 -1,
+												 elem);
+				}
+				PG_CATCH();
+				{
+					Py_DECREF(elem);
+					PG_RE_THROW();
+				}
+				PG_END_TRY();
+
+				Py_DECREF(elem);
+				nulls[j] = ' ';
+			}
+			else
+			{
+				Py_DECREF(elem);
+				plan->values[j] =
+					InputFunctionCall(&(plan->args[j].out.d.typfunc),
+									  NULL,
+									  plan->args[j].out.d.typioparam,
+									  -1);
+				nulls[j] = 'n';
+			}
+		}
+
+		portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
+								 PLy_curr_procedure->fn_readonly);
+		if (portal == NULL)
+			elog(ERROR, "SPI_cursor_open() failed:%s",
+				 SPI_result_code_string(SPI_result));
+
+		cursor->portalname = PLy_strdup(portal->name);
+
+		PLy_spi_subtransaction_commit(oldcontext, oldowner);
+	}
+	PG_CATCH();
+	{
+		int			k;
+
+		/* cleanup plan->values array */
+		for (k = 0; k < nargs; k++)
+		{
+			if (!plan->args[k].out.d.typbyval &&
+				(plan->values[k] != PointerGetDatum(NULL)))
+			{
+				pfree(DatumGetPointer(plan->values[k]));
+				plan->values[k] = PointerGetDatum(NULL);
+			}
+		}
+
+		Py_DECREF(cursor);
+
+		PLy_spi_subtransaction_abort(oldcontext, oldowner);
+		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);
+		}
+	}
+
+	Assert(cursor->portalname != NULL);
+	return (PyObject *) cursor;
+}
+
+static void
+PLy_cursor_dealloc(PyObject *arg)
+{
+	PLyCursorObject *cursor;
+	Portal			portal;
+
+	cursor = (PLyCursorObject *) arg;
+
+	if (!cursor->closed)
+	{
+		portal = GetPortalByName(cursor->portalname);
+
+		if (PortalIsValid(portal))
+			SPI_cursor_close(portal);
+	}
+
+	PLy_free(cursor->portalname);
+	cursor->portalname = NULL;
+
+	PLy_typeinfo_dealloc(&cursor->result);
+	arg->ob_type->tp_free(arg);
+}
+
+static PyObject *
+PLy_cursor_iternext(PyObject *self)
+{
+	PLyCursorObject *cursor;
+	PyObject		*ret;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+	Portal			portal;
+
+	cursor = (PLyCursorObject *) self;
+
+	if (cursor->closed)
+	{
+		PLy_exception_set(PyExc_ValueError, "iterating a closed cursor");
+		return NULL;
+	}
+
+	portal = GetPortalByName(cursor->portalname);
+	if (!PortalIsValid(portal))
+	{
+		PLy_exception_set(PyExc_ValueError,
+						  "iterating a cursor in an aborted subtransaction");
+		return NULL;
+	}
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		SPI_cursor_fetch(portal, true, 1);
+		if (SPI_processed == 0)
+		{
+			PyErr_SetNone(PyExc_StopIteration);
+			ret = NULL;
+		}
+		else
+		{
+			if (cursor->result.is_rowtype != 1)
+				PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
+
+			ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0],
+									SPI_tuptable->tupdesc);
+		}
+
+		SPI_freetuptable(SPI_tuptable);
+
+		PLy_spi_subtransaction_commit(oldcontext, oldowner);
+	}
+	PG_CATCH();
+	{
+		SPI_freetuptable(SPI_tuptable);
+
+		PLy_spi_subtransaction_abort(oldcontext, oldowner);
+		return NULL;
+	}
+	PG_END_TRY();
+
+	return ret;
+}
+
+static PyObject *
+PLy_cursor_fetch(PyObject *self, PyObject *args)
+{
+	PLyCursorObject *cursor;
+	int				count;
+	PLyResultObject	*ret;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+	Portal			portal;
+
+	if (!PyArg_ParseTuple(args, "i", &count))
+		return NULL;
+
+	cursor = (PLyCursorObject *) self;
+
+	if (cursor->closed)
+	{
+		PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor");
+		return NULL;
+	}
+
+	portal = GetPortalByName(cursor->portalname);
+	if (!PortalIsValid(portal))
+	{
+		PLy_exception_set(PyExc_ValueError,
+						  "iterating a cursor in an aborted subtransaction");
+		return NULL;
+	}
+
+	ret = (PLyResultObject *) PLy_result_new();
+	if (ret == NULL)
+		return NULL;
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		SPI_cursor_fetch(portal, true, count);
+
+		if (cursor->result.is_rowtype != 1)
+			PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc);
+
+		Py_DECREF(ret->status);
+		ret->status = PyInt_FromLong(SPI_OK_FETCH);
+
+		Py_DECREF(ret->nrows);
+		ret->nrows = PyInt_FromLong(SPI_processed);
+
+		if (SPI_processed != 0)
+		{
+			int	i;
+
+			Py_DECREF(ret->rows);
+			ret->rows = PyList_New(SPI_processed);
+
+			for (i = 0; i < SPI_processed; i++)
+			{
+				PyObject   *row = PLyDict_FromTuple(&cursor->result,
+													SPI_tuptable->vals[i],
+													SPI_tuptable->tupdesc);
+				PyList_SetItem(ret->rows, i, row);
+			}
+		}
+
+		SPI_freetuptable(SPI_tuptable);
+
+		PLy_spi_subtransaction_commit(oldcontext, oldowner);
+	}
+	PG_CATCH();
+	{
+		SPI_freetuptable(SPI_tuptable);
+
+		PLy_spi_subtransaction_abort(oldcontext, oldowner);
+		return NULL;
+	}
+	PG_END_TRY();
+
+	return (PyObject *) ret;
+}
+
+static PyObject *
+PLy_cursor_close(PyObject *self, PyObject *unused)
+{
+	PLyCursorObject *cursor = (PLyCursorObject *) self;
+
+	if (!cursor->closed)
+	{
+		Portal portal = GetPortalByName(cursor->portalname);
+
+		if (!PortalIsValid(portal))
+		{
+			PLy_exception_set(PyExc_ValueError,
+							  "closing a cursor in an aborted subtransaction");
+			return NULL;
+		}
+
+		SPI_cursor_close(portal);
+		cursor->closed = true;
+	}
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}

src/pl/plpython/plpy_cursorobject.h

+/*
+ * src/pl/plpython/plpy_cursorobject.h
+ */
+
+#ifndef PLPY_CURSOROBJECT_H
+#define PLPY_CURSOROBJECT_H
+
+#include "plpy_typeio.h"
+
+
+typedef struct PLyCursorObject
+{
+	PyObject_HEAD
+	char		*portalname;
+	PLyTypeInfo result;
+	bool		closed;
+} PLyCursorObject;
+
+extern void PLy_cursor_init_type(void);
+extern PyObject *PLy_cursor(PyObject *, PyObject *);
+
+#endif	/* PLPY_CURSOROBJECT_H */

src/pl/plpython/plpy_elog.c

+/*
+ * reporting Python exceptions as PostgreSQL errors
+ *
+ * src/pl/plpython/plpy_elog.c
+ */
+
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+
+#include "plpython.h"
+
+#include "plpy_elog.h"
+
+#include "plpy_procedure.h"
+
+
+PyObject *PLy_exc_error = NULL;
+PyObject *PLy_exc_fatal = NULL;
+PyObject *PLy_exc_spi_error = NULL;
+
+
+static void PLy_traceback(char **, char **, int *);
+static void PLy_get_spi_error_data(PyObject *, int *, char **,
+								   char **, char **, int *);
+static char * get_source_line(const char *, int);
+
+
+/*
+ * Emit a PG error or notice, together with any available info about
+ * the current Python error, previously set by PLy_exception_set().
+ * This should be used to propagate Python errors into PG.	If fmt is
+ * NULL, the Python error becomes the primary error message, otherwise
+ * it becomes the detail.  If there is a Python traceback, it is put
+ * in the context.
+ */
+void
+PLy_elog(int elevel, const char *fmt,...)
+{
+	char	   *xmsg;
+	char	   *tbmsg;
+	int			tb_depth;
+	StringInfoData emsg;
+	PyObject   *exc,
+			   *val,
+			   *tb;
+	const char *primary = NULL;
+	int		   sqlerrcode = 0;
+	char	   *detail = NULL;
+	char	   *hint = NULL;
+	char	   *query = NULL;
+	int			position = 0;
+
+	PyErr_Fetch(&exc, &val, &tb);
+	if (exc != NULL)
+	{
+		if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
+			PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
+		else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
+			elevel = FATAL;
+	}
+	PyErr_Restore(exc, val, tb);
+
+	PLy_traceback(&xmsg, &tbmsg, &tb_depth);
+
+	if (fmt)
+	{
+		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);
+		}
+		primary = emsg.data;
+
+		/* Since we have a format string, we cannot have a SPI detail. */
+		Assert(detail == NULL);
+
+		/* If there's an exception message, it goes in the detail. */
+		if (xmsg)
+			detail = xmsg;
+	}
+	else
+	{
+		if (xmsg)
+			primary = xmsg;
+	}
+
+	PG_TRY();
+	{
+		ereport(elevel,
+				(errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR),
+				 errmsg_internal("%s", primary ? primary : "no exception data"),
+				 (detail) ? errdetail_internal("%s", detail) : 0,
+				 (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
+				 (hint) ? errhint("%s", hint) : 0,
+				 (query) ? internalerrquery(query) : 0,
+				 (position) ? internalerrposition(position) : 0));
+	}
+	PG_CATCH();
+	{
+		if (fmt)
+			pfree(emsg.data);
+		if (xmsg)
+			pfree(xmsg);
+		if (tbmsg)
+			pfree(tbmsg);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	if (fmt)
+		pfree(emsg.data);
+	if (xmsg)
+		pfree(xmsg);
+	if (tbmsg)
+		pfree(tbmsg);
+}
+
+/*
+ * Extract a Python traceback from the current exception.
+ *
+ * The exception error message is returned in xmsg, the traceback in
+ * tbmsg (both as palloc'd strings) and the traceback depth in
+ * tb_depth.
+ */
+static void
+PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
+{
+	PyObject   *e,
+			   *v,
+			   *tb;
+	PyObject   *e_type_o;
+	PyObject   *e_module_o;
+	char	   *e_type_s = NULL;
+	char	   *e_module_s = NULL;
+	PyObject   *vob = NULL;
+	char	   *vstr;
+	StringInfoData xstr;
+	StringInfoData tbstr;
+
+	/*
+	 * get the current exception
+	 */
+	PyErr_Fetch(&e, &v, &tb);
+
+	/*
+	 * oops, no exception, return
+	 */
+	if (e == NULL)
+	{
+		*xmsg = NULL;
+		*tbmsg = NULL;
+		*tb_depth = 0;
+
+		return;
+	}
+
+	PyErr_NormalizeException(&e, &v, &tb);
+
+	/*
+	 * Format the exception and its value and put it in xmsg.
+	 */
+
+	e_type_o = PyObject_GetAttrString(e, "__name__");
+	e_module_o = PyObject_GetAttrString(e, "__module__");
+	if (e_type_o)
+		e_type_s = PyString_AsString(e_type_o);
+	if (e_type_s)
+		e_module_s = PyString_AsString(e_module_o);
+
+	if (v && ((vob = PyObject_Str(v)) != NULL))
+		vstr = PyString_AsString(vob);
+	else
+		vstr = "unknown";
+
+	initStringInfo(&xstr);
+	if (!e_type_s || !e_module_s)
+	{
+		if (PyString_Check(e))
+			/* deprecated string exceptions */
+			appendStringInfoString(&xstr, PyString_AsString(e));
+		else
+			/* shouldn't happen */
+			appendStringInfoString(&xstr, "unrecognized exception");
+	}
+	/* mimics behavior of traceback.format_exception_only */
+	else if (strcmp(e_module_s, "builtins") == 0
+			 || strcmp(e_module_s, "__main__") == 0
+			 || strcmp(e_module_s, "exceptions") == 0)
+		appendStringInfo(&xstr, "%s", e_type_s);
+	else
+		appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
+	appendStringInfo(&xstr, ": %s", vstr);
+
+	*xmsg = xstr.data;
+
+	/*
+	 * Now format the traceback and put it in tbmsg.
+	 */
+
+	*tb_depth = 0;
+	initStringInfo(&tbstr);
+	/* Mimick Python traceback reporting as close as possible. */
+	appendStringInfoString(&tbstr, "Traceback (most recent call last):");
+	while (tb != NULL && tb != Py_None)
+	{
+		PyObject   *volatile tb_prev = NULL;
+		PyObject   *volatile frame = NULL;
+		PyObject   *volatile code = NULL;
+		PyObject   *volatile name = NULL;
+		PyObject   *volatile lineno = NULL;
+		PyObject   *volatile filename = NULL;
+
+		PG_TRY();
+		{
+			lineno = PyObject_GetAttrString(tb, "tb_lineno");
+			if (lineno == NULL)
+				elog(ERROR, "could not get line number from Python traceback");
+
+			frame = PyObject_GetAttrString(tb, "tb_frame");
+			if (frame == NULL)
+				elog(ERROR, "could not get frame from Python traceback");
+
+			code = PyObject_GetAttrString(frame, "f_code");
+			if (code == NULL)
+				elog(ERROR, "could not get code object from Python frame");
+
+			name = PyObject_GetAttrString(code, "co_name");
+			if (name == NULL)
+				elog(ERROR, "could not get function name from Python code object");
+
+			filename = PyObject_GetAttrString(code, "co_filename");
+			if (filename == NULL)
+				elog(ERROR, "could not get file name from Python code object");
+		}
+		PG_CATCH();
+		{
+			Py_XDECREF(frame);
+			Py_XDECREF(code);
+			Py_XDECREF(name);
+			Py_XDECREF(lineno);
+			Py_XDECREF(filename);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		/* The first frame always points at <module>, skip it. */
+		if (*tb_depth > 0)
+		{
+			char	   *proname;
+			char	   *fname;
+			char	   *line;
+			char	   *plain_filename;
+			long		plain_lineno;
+
+			/*
+			 * The second frame points at the internal function, but to mimick
+			 * Python error reporting we want to say <module>.
+			 */
+			if (*tb_depth == 1)
+				fname = "<module>";
+			else
+				fname = PyString_AsString(name);
+
+			proname = PLy_procedure_name(PLy_curr_procedure);
+			plain_filename = PyString_AsString(filename);
+			plain_lineno = PyInt_AsLong(lineno);
+
+			if (proname == NULL)
+				appendStringInfo(
+				&tbstr, "\n  PL/Python anonymous code block, line %ld, in %s",
+								 plain_lineno - 1, fname);
+			else
+				appendStringInfo(
+					&tbstr, "\n  PL/Python function \"%s\", line %ld, in %s",
+								 proname, plain_lineno - 1, fname);
+
+			/*
+			 * function code object was compiled with "<string>" as the
+			 * filename
+			 */
+			if (PLy_curr_procedure && plain_filename != NULL &&
+				strcmp(plain_filename, "<string>") == 0)
+			{
+				/*
+				 * If we know the current procedure, append the exact line
+				 * from the source, again mimicking Python's traceback.py
+				 * module behavior.  We could store the already line-split
+				 * source to avoid splitting it every time, but producing a
+				 * traceback is not the most important scenario to optimize
+				 * for.  But we do not go as far as traceback.py in reading
+				 * the source of imported modules.
+				 */
+				line = get_source_line(PLy_curr_procedure->src, plain_lineno);
+				if (line)
+				{
+					appendStringInfo(&tbstr, "\n    %s", line);
+					pfree(line);
+				}
+			}
+		}
+
+		Py_DECREF(frame);
+		Py_DECREF(code);
+		Py_DECREF(name);
+		Py_DECREF(lineno);
+		Py_DECREF(filename);
+
+		/* Release the current frame and go to the next one. */
+		tb_prev = tb;
+		tb = PyObject_GetAttrString(tb, "tb_next");
+		Assert(tb_prev != Py_None);
+		Py_DECREF(tb_prev);
+		if (tb == NULL)
+			elog(ERROR, "could not traverse Python traceback");
+		(*tb_depth)++;
+	}
+
+	/* Return the traceback. */
+	*tbmsg = tbstr.data;
+
+	Py_XDECREF(e_type_o);
+	Py_XDECREF(e_module_o);
+	Py_XDECREF(vob);
+	Py_XDECREF(v);
+	Py_DECREF(e);
+}
+
+/*
+ * Extract the error data from a SPIError
+ */
+static void
+PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
+{
+	PyObject   *spidata = NULL;
+
+	spidata = PyObject_GetAttrString(exc, "spidata");
+	if (!spidata)
+		goto cleanup;
+
+	if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position))
+		goto cleanup;
+
+cleanup:
+	PyErr_Clear();
+	/* no elog here, we simply won't report the errhint, errposition etc */
+	Py_XDECREF(spidata);
+}
+
+/*
+ * Get the given source line as a palloc'd string
+ */
+static char *
+get_source_line(const char *src, int lineno)
+{
+	const char *s = NULL;
+	const char *next = src;
+	int			current = 0;
+
+	while (current < lineno)
+	{
+		s = next;
+		next = strchr(s + 1, '\n');
+		current++;
+		if (next == NULL)
+			break;
+	}
+
+	if (current != lineno)
+		return NULL;
+
+	while (*s && isspace((unsigned char) *s))
+		s++;
+
+	if (next == NULL)
+		return pstrdup(s);
+
+	/*
+	 * Sanity check, next < s if the line was all-whitespace, which should
+	 * never happen if Python reported a frame created on that line, but check
+	 * anyway.
+	 */
+	if (next < s)
+		return NULL;
+
+	return pnstrdup(s, next - s);
+}
+
+
+/* call PyErr_SetString with a vprint interface and translation support */
+void
+PLy_exception_set(PyObject *exc, const char *fmt,...)
+{
+	char		buf[1024];
+	va_list		ap;
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
+	va_end(ap);
+
+	PyErr_SetString(exc, buf);
+}
+
+/* same, with pluralized message */
+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);
+}

src/pl/plpython/plpy_elog.h

+/*
+ * src/pl/plpython/plpy_elog.h
+ */
+
+#ifndef PLPY_ELOG_H
+#define PLPY_ELOG_H
+
+/* global exception classes */
+extern PyObject *PLy_exc_error;
+extern PyObject *PLy_exc_fatal;
+extern PyObject *PLy_exc_spi_error;
+
+extern void PLy_elog(int, const char *,...)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+
+extern void PLy_exception_set(PyObject *, const char *,...)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+
+extern void PLy_exception_set_plural(PyObject *, const char *, const char *,
+									 unsigned long n,...)
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 5)))
+__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 5)));
+
+#endif	/* PLPY_ELOG_H */

src/pl/plpython/plpy_exec.c

+/*
+ * executing Python code
+ *
+ * src/pl/plpython/plpy_exec.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/typcache.h"
+
+#include "plpython.h"
+
+#include "plpy_exec.h"
+
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_procedure.h"
+#include "plpy_subxactobject.h"
+
+
+static PyObject *PLy_function_build_args(FunctionCallInfo, PLyProcedure *);
+static void PLy_function_delete_args(PLyProcedure *);
+static void plpython_return_error_callback(void *);
+
+static PyObject *PLy_trigger_build_args(FunctionCallInfo, PLyProcedure *,
+										HeapTuple *);
+static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
+								  TriggerData *, HeapTuple);
+static void plpython_trigger_error_callback(void *);
+
+static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *);
+static void PLy_abort_open_subtransactions(int);
+
+
+/* function subhandler */
+Datum
+PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
+{
+	Datum		rv;
+	PyObject   *volatile plargs = NULL;
+	PyObject   *volatile plrv = NULL;
+	ErrorContextCallback plerrcontext;
+
+	PG_TRY();
+	{
+		if (!proc->is_setof || proc->setof == NULL)
+		{
+			/*
+			 * Simple type returning function or first time for SETOF
+			 * function: actually execute the 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);
+		}
+
+		/*
+		 * If it returns a set, call the iterator to get the next return item.
+		 * We stay in the SPI context while doing this, because PyIter_Next()
+		 * calls back into Python code which might contain SPI calls.
+		 */
+		if (proc->is_setof)
+		{
+			bool		has_error = false;
+			ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+			if (proc->setof == NULL)
+			{
+				/* first time -- do checks and setup */
+				if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+					(rsi->allowedModes & SFRM_ValuePerCall) == 0)
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unsupported set function return mode"),
+							 errdetail("PL/Python set-returning functions only support returning only value per call.")));
+				}
+				rsi->returnMode = SFRM_ValuePerCall;
+
+				/* Make iterator out of returned object */
+				proc->setof = PyObject_GetIter(plrv);
+				Py_DECREF(plrv);
+				plrv = NULL;
+
+				if (proc->setof == NULL)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("returned object cannot be iterated"),
+							 errdetail("PL/Python set-returning functions must return an iterable object.")));
+			}
+
+			/* Fetch next from iterator */
+			plrv = PyIter_Next(proc->setof);
+			if (plrv)
+				rsi->isDone = ExprMultipleResult;
+			else
+			{
+				rsi->isDone = ExprEndResult;
+				has_error = PyErr_Occurred() != NULL;
+			}
+
+			if (rsi->isDone == ExprEndResult)
+			{
+				/* Iterator is exhausted or error happened */
+				Py_DECREF(proc->setof);
+				proc->setof = NULL;
+
+				Py_XDECREF(plargs);
+				Py_XDECREF(plrv);
+
+				PLy_function_delete_args(proc);
+
+				if (has_error)
+					PLy_elog(ERROR, "error fetching next item from iterator");
+
+				/* Disconnect from the SPI manager before returning */
+				if (SPI_finish() != SPI_OK_FINISH)
+					elog(ERROR, "SPI_finish failed");
+
+				fcinfo->isnull = true;
+				return (Datum) NULL;
+			}
+		}
+
+		/*
+		 * 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");
+
+		plerrcontext.callback = plpython_return_error_callback;
+		plerrcontext.previous = error_context_stack;
+		error_context_stack = &plerrcontext;
+
+		/*
+		 * 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("PL/Python function with return type \"void\" did not return None")));
+
+			fcinfo->isnull = false;
+			rv = (Datum) 0;
+		}
+		else if (plrv == Py_None)
+		{
+			fcinfo->isnull = true;
+			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)
+		{
+			TupleDesc	desc;
+			HeapTuple	tuple = NULL;
+
+			/* make sure it's not an unnamed record */
+			Assert((proc->result.out.d.typoid == RECORDOID &&
+					proc->result.out.d.typmod != -1) ||
+				   (proc->result.out.d.typoid != RECORDOID &&
+					proc->result.out.d.typmod == -1));
+
+			desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid,
+										  proc->result.out.d.typmod);
+
+			tuple = PLyObject_ToTuple(&proc->result, desc, plrv);
+
+			if (tuple != NULL)
+			{
+				fcinfo->isnull = false;
+				rv = HeapTupleGetDatum(tuple);
+			}
+			else
+			{
+				fcinfo->isnull = true;
+				rv = (Datum) NULL;
+			}
+		}
+		else
+		{
+			fcinfo->isnull = false;
+			rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv);
+		}
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plargs);
+		Py_XDECREF(plrv);
+
+		/*
+		 * If there was an error the iterator might have not been exhausted
+		 * yet. Set it to NULL so the next invocation of the function will
+		 * start the iteration again.
+		 */
+		Py_XDECREF(proc->setof);
+		proc->setof = NULL;
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	error_context_stack = plerrcontext.previous;
+
+	Py_XDECREF(plargs);
+	Py_DECREF(plrv);
+
+	return rv;
+}
+
+/* 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;
+}
+
+/* helper functions for Python code execution */
+
+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
+				{
+					arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
+													 fcinfo->arg[i]);
+				}
+			}
+
+			if (arg == NULL)
+			{
+				Py_INCREF(Py_None);
+				arg = Py_None;
+			}
+
+			if (PyList_SetItem(args, i, arg) == -1)
+				PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments");
+
+			if (proc->argnames && proc->argnames[i] &&
+			PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1)
+				PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments");
+			arg = NULL;
+		}
+
+		/* Set up output conversion for functions returning RECORD */
+		if (proc->result.out.d.typoid == RECORDOID)
+		{
+			TupleDesc	desc;
+
+			if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("function returning record called in context "
+								"that cannot accept type record")));
+
+			/* cache the output conversion functions */
+			PLy_output_record_funcs(&(proc->result), desc);
+		}
+	}
+	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++)
+		if (proc->argnames[i])
+			PyDict_DelItemString(proc->globals, proc->argnames[i]);
+}
+
+static void
+plpython_return_error_callback(void *arg)
+{
+	if (PLy_curr_procedure)
+		errcontext("while creating return value");
+}
+
+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 create new dictionary while building trigger arguments");
+
+		pltname = PyString_FromString(tdata->tg_trigger->tgname);
+		PyDict_SetItemString(pltdata, "name", pltname);
+		Py_DECREF(pltname);
+
+		stroid = DatumGetCString(DirectFunctionCall1(oidout,
+							   ObjectIdGetDatum(tdata->tg_relation->rd_id)));
+		pltrelid = PyString_FromString(stroid);
+		PyDict_SetItemString(pltdata, "relid", pltrelid);
+		Py_DECREF(pltrelid);
+		pfree(stroid);
+
+		stroid = SPI_getrelname(tdata->tg_relation);
+		plttablename = PyString_FromString(stroid);
+		PyDict_SetItemString(pltdata, "table_name", plttablename);
+		Py_DECREF(plttablename);
+		pfree(stroid);
+
+		stroid = SPI_getnspname(tdata->tg_relation);
+		plttableschema = PyString_FromString(stroid);
+		PyDict_SetItemString(pltdata, "table_schema", plttableschema);
+		Py_DECREF(plttableschema);
+		pfree(stroid);
+
+		if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
+			pltwhen = PyString_FromString("BEFORE");
+		else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
+			pltwhen = PyString_FromString("AFTER");
+		else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+			pltwhen = PyString_FromString("INSTEAD OF");
+		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 if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+				pltevent = PyString_FromString("TRUNCATE");
+			else
+			{
+				elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
+				pltevent = NULL;	/* keep compiler quiet */
+			}
+
+			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;
+}
+
+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;
+	ErrorContextCallback plerrcontext;
+
+	plerrcontext.callback = plpython_trigger_error_callback;
+	plerrcontext.previous = error_context_stack;
+	error_context_stack = &plerrcontext;
+
+	plntup = plkeys = platt = plval = plstr = NULL;
+	modattrs = NULL;
+	modvalues = NULL;
+	modnulls = NULL;
+
+	PG_TRY();
+	{
+		if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL)
+			ereport(ERROR,
+					(errmsg("TD[\"new\"] deleted, cannot modify row")));
+		if (!PyDict_Check(plntup))
+			ereport(ERROR,
+					(errmsg("TD[\"new\"] is not a dictionary")));
+		Py_INCREF(plntup);
+
+		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	   *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,
+						(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,
+						(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);
+		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);
+
+	error_context_stack = plerrcontext.previous;
+
+	return rtup;
+}
+
+static void
+plpython_trigger_error_callback(void *arg)
+{
+	if (PLy_curr_procedure)
+		errcontext("while modifying trigger row");
+}
+
+/* execute Python code, propagate Python errors to the backend */
+static PyObject *
+PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs)
+{
+	PyObject   *rv;
+	int volatile save_subxact_level = list_length(explicit_subtransactions);
+
+	PyDict_SetItemString(proc->globals, kargs, vargs);
+
+	PG_TRY();
+	{
+#if PY_VERSION_HEX >= 0x03020000
+		rv = PyEval_EvalCode(proc->code,
+							 proc->globals, proc->globals);
+#else
+		rv = PyEval_EvalCode((PyCodeObject *) proc->code,
+							 proc->globals, proc->globals);
+#endif
+
+		/*
+		 * Since plpy will only let you close subtransactions that you
+		 * started, you cannot *unnest* subtransactions, only *nest* them
+		 * without closing.
+		 */
+		Assert(list_length(explicit_subtransactions) >= save_subxact_level);
+	}
+	PG_CATCH();
+	{
+		PLy_abort_open_subtransactions(save_subxact_level);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	PLy_abort_open_subtransactions(save_subxact_level);
+
+	/* If the Python code returned an error, propagate it */
+	if (rv == NULL)
+		PLy_elog(ERROR, NULL);
+
+	return rv;
+}
+
+/*
+ * Abort lingering subtransactions that have been explicitly started
+ * by plpy.subtransaction().start() and not properly closed.
+ */
+static void
+PLy_abort_open_subtransactions(int save_subxact_level)
+{
+	Assert(save_subxact_level >= 0);
+
+	while (list_length(explicit_subtransactions) > save_subxact_level)
+	{
+		PLySubtransactionData *subtransactiondata;
+
+		Assert(explicit_subtransactions != NIL);
+
+		ereport(WARNING,
+				(errmsg("forcibly aborting a subtransaction that has not been exited")));
+
+		RollbackAndReleaseCurrentSubTransaction();
+
+		SPI_restore_connection();
+
+		subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions);
+		explicit_subtransactions = list_delete_first(explicit_subtransactions);
+
+		MemoryContextSwitchTo(subtransactiondata->oldcontext);
+		CurrentResourceOwner = subtransactiondata->oldowner;
+		PLy_free(subtransactiondata);
+	}
+}

src/pl/plpython/plpy_exec.h

+/*
+ * src/pl/plpython/plpy_exec.h
+ */
+
+#ifndef PLPY_EXEC_H
+#define PLPY_EXEC_H
+
+#include "plpy_procedure.h"
+
+extern Datum PLy_exec_function(FunctionCallInfo, PLyProcedure *);
+extern HeapTuple PLy_exec_trigger(FunctionCallInfo, PLyProcedure *);
+
+#endif	/* PLPY_EXEC_H */

src/pl/plpython/plpy_main.c

+/*
+ * PL/Python main entry points
+ *
+ * src/pl/plpython/plpy_main.c
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "utils/guc.h"
+#include "utils/syscache.h"
+
+#include "plpython.h"
+
+#include "plpy_main.h"
+
+#include "plpy_elog.h"
+#include "plpy_exec.h"
+#include "plpy_plpymodule.h"
+#include "plpy_procedure.h"
+#include "plpy_subxactobject.h"
+
+
+/*
+ * exported functions
+ */
+
+#if PY_MAJOR_VERSION >= 3
+/* Use separate names to avoid clash in pg_pltemplate */
+#define plpython_validator plpython3_validator
+#define plpython_call_handler plpython3_call_handler
+#define plpython_inline_handler plpython3_inline_handler
+#endif
+
+extern void _PG_init(void);
+extern Datum plpython_validator(PG_FUNCTION_ARGS);
+extern Datum plpython_call_handler(PG_FUNCTION_ARGS);
+extern Datum plpython_inline_handler(PG_FUNCTION_ARGS);
+
+#if PY_MAJOR_VERSION < 3
+/* Define aliases plpython2_call_handler etc */
+extern Datum plpython2_validator(PG_FUNCTION_ARGS);
+extern Datum plpython2_call_handler(PG_FUNCTION_ARGS);
+extern Datum plpython2_inline_handler(PG_FUNCTION_ARGS);
+#endif
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plpython_validator);
+PG_FUNCTION_INFO_V1(plpython_call_handler);
+PG_FUNCTION_INFO_V1(plpython_inline_handler);
+
+#if PY_MAJOR_VERSION < 3
+PG_FUNCTION_INFO_V1(plpython2_validator);
+PG_FUNCTION_INFO_V1(plpython2_call_handler);
+PG_FUNCTION_INFO_V1(plpython2_inline_handler);
+#endif
+
+
+static bool PLy_procedure_is_trigger(Form_pg_proc);
+static void plpython_error_callback(void *);
+static void plpython_inline_error_callback(void *);
+static void PLy_init_interp(void);
+
+static const int plpython_python_version = PY_MAJOR_VERSION;
+
+/* initialize global variables */
+PyObject *PLy_interp_globals = NULL;
+
+
+void
+_PG_init(void)
+{
+	/* Be sure we do initialization only once (should be redundant now) */
+	static bool inited = false;
+	const int **version_ptr;
+
+	if (inited)
+		return;
+
+	/* Be sure we don't run Python 2 and 3 in the same session (might crash) */
+	version_ptr = (const int **) find_rendezvous_variable("plpython_python_version");
+	if (!(*version_ptr))
+		*version_ptr = &plpython_python_version;
+	else
+	{
+		if (**version_ptr != plpython_python_version)
+			ereport(FATAL,
+					(errmsg("Python major version mismatch in session"),
+					 errdetail("This session has previously used Python major version %d, and it is now attempting to use Python major version %d.",
+							   **version_ptr, plpython_python_version),
+					 errhint("Start a new session to use a different Python major version.")));
+	}
+
+	pg_bindtextdomain(TEXTDOMAIN);
+
+#if PY_MAJOR_VERSION >= 3
+	PyImport_AppendInittab("plpy", PyInit_plpy);
+#endif
+	Py_Initialize();
+#if PY_MAJOR_VERSION >= 3
+	PyImport_ImportModule("plpy");
+#endif
+	PLy_init_interp();
+	PLy_init_plpy();
+	if (PyErr_Occurred())
+		PLy_elog(FATAL, "untrapped error in initialization");
+
+	init_procedure_caches();
+
+	explicit_subtransactions = NIL;
+
+	inited = true;
+}
+
+/*
+ * This should only be called once from _PG_init. Initialize the Python
+ * interpreter and global data.
+ */
+void
+PLy_init_interp(void)
+{
+	static PyObject *PLy_interp_safe_globals = NULL;
+	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");
+}
+
+Datum
+plpython_validator(PG_FUNCTION_ARGS)
+{
+	Oid			funcoid = PG_GETARG_OID(0);
+	HeapTuple	tuple;
+	Form_pg_proc procStruct;
+	bool		is_trigger;
+
+	if (!check_function_bodies)
+	{
+		PG_RETURN_VOID();
+	}
+
+	/* Get the new function's pg_proc entry */
+	tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for function %u", funcoid);
+	procStruct = (Form_pg_proc) GETSTRUCT(tuple);
+
+	is_trigger = PLy_procedure_is_trigger(procStruct);
+
+	ReleaseSysCache(tuple);
+
+	PLy_procedure_get(funcoid, is_trigger);
+
+	PG_RETURN_VOID();
+}
+
+#if PY_MAJOR_VERSION < 3
+Datum
+plpython2_validator(PG_FUNCTION_ARGS)
+{
+	return plpython_validator(fcinfo);
+}
+#endif   /* PY_MAJOR_VERSION < 3 */
+
+Datum
+plpython_call_handler(PG_FUNCTION_ARGS)
+{
+	Datum		retval;
+	PLyProcedure *save_curr_proc;
+	ErrorContextCallback plerrcontext;
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	save_curr_proc = PLy_curr_procedure;
+
+	/*
+	 * Setup error traceback support for ereport()
+	 */
+	plerrcontext.callback = plpython_error_callback;
+	plerrcontext.previous = error_context_stack;
+	error_context_stack = &plerrcontext;
+
+	PG_TRY();
+	{
+		PLyProcedure *proc;
+
+		if (CALLED_AS_TRIGGER(fcinfo))
+		{
+			HeapTuple	trv;
+
+			proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true);
+			PLy_curr_procedure = proc;
+			trv = PLy_exec_trigger(fcinfo, proc);
+			retval = PointerGetDatum(trv);
+		}
+		else
+		{
+			proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
+			PLy_curr_procedure = proc;
+			retval = PLy_exec_function(fcinfo, proc);
+		}
+	}
+	PG_CATCH();
+	{
+		PLy_curr_procedure = save_curr_proc;
+		PyErr_Clear();
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	/* Pop the error context stack */
+	error_context_stack = plerrcontext.previous;
+
+	PLy_curr_procedure = save_curr_proc;
+
+	return retval;
+}
+
+#if PY_MAJOR_VERSION < 3
+Datum
+plpython2_call_handler(PG_FUNCTION_ARGS)
+{
+	return plpython_call_handler(fcinfo);
+}
+#endif   /* PY_MAJOR_VERSION < 3 */
+
+Datum
+plpython_inline_handler(PG_FUNCTION_ARGS)
+{
+	InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
+	FunctionCallInfoData fake_fcinfo;
+	FmgrInfo	flinfo;
+	PLyProcedure *save_curr_proc;
+	PLyProcedure proc;
+	ErrorContextCallback plerrcontext;
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	save_curr_proc = PLy_curr_procedure;
+
+	/*
+	 * Setup error traceback support for ereport()
+	 */
+	plerrcontext.callback = plpython_inline_error_callback;
+	plerrcontext.previous = error_context_stack;
+	error_context_stack = &plerrcontext;
+
+	MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
+	MemSet(&flinfo, 0, sizeof(flinfo));
+	fake_fcinfo.flinfo = &flinfo;
+	flinfo.fn_oid = InvalidOid;
+	flinfo.fn_mcxt = CurrentMemoryContext;
+
+	MemSet(&proc, 0, sizeof(PLyProcedure));
+	proc.pyname = PLy_strdup("__plpython_inline_block");
+	proc.result.out.d.typoid = VOIDOID;
+
+	PG_TRY();
+	{
+		PLy_procedure_compile(&proc, codeblock->source_text);
+		PLy_curr_procedure = &proc;
+		PLy_exec_function(&fake_fcinfo, &proc);
+	}
+	PG_CATCH();
+	{
+		PLy_procedure_delete(&proc);
+		PLy_curr_procedure = save_curr_proc;
+		PyErr_Clear();
+		PG_RE_THROW();
+	}