From 147c2482542868d1f9dcf7d2ecfeac58d845335c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sun, 18 Dec 2011 21:14:16 +0200
Subject: [PATCH] Split plpython.c into smaller pieces
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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
---
 src/pl/plpython/Makefile             |   16 +-
 src/pl/plpython/nls.mk               |    3 +-
 src/pl/plpython/plpy_cursorobject.c  |  492 +++
 src/pl/plpython/plpy_cursorobject.h  |   22 +
 src/pl/plpython/plpy_elog.c          |  428 ++
 src/pl/plpython/plpy_elog.h          |   24 +
 src/pl/plpython/plpy_exec.c          |  859 ++++
 src/pl/plpython/plpy_exec.h          |   13 +
 src/pl/plpython/plpy_main.c          |  325 ++
 src/pl/plpython/plpy_main.h          |   13 +
 src/pl/plpython/plpy_planobject.c    |  128 +
 src/pl/plpython/plpy_planobject.h    |   26 +
 src/pl/plpython/plpy_plpymodule.c    |  417 ++
 src/pl/plpython/plpy_plpymodule.h    |   19 +
 src/pl/plpython/plpy_procedure.c     |  531 +++
 src/pl/plpython/plpy_procedure.h     |   52 +
 src/pl/plpython/plpy_resultobject.c  |  180 +
 src/pl/plpython/plpy_resultobject.h  |   20 +
 src/pl/plpython/plpy_spi.c           |  559 +++
 src/pl/plpython/plpy_spi.h           |   25 +
 src/pl/plpython/plpy_subxactobject.c |  217 +
 src/pl/plpython/plpy_subxactobject.h |   29 +
 src/pl/plpython/plpy_typeio.c        | 1038 +++++
 src/pl/plpython/plpy_typeio.h        |  107 +
 src/pl/plpython/plpy_util.c          |  125 +
 src/pl/plpython/plpy_util.h          |   21 +
 src/pl/plpython/plpython.c           | 5439 --------------------------
 src/pl/plpython/plpython.h           |  156 +
 28 files changed, 5842 insertions(+), 5442 deletions(-)
 create mode 100644 src/pl/plpython/plpy_cursorobject.c
 create mode 100644 src/pl/plpython/plpy_cursorobject.h
 create mode 100644 src/pl/plpython/plpy_elog.c
 create mode 100644 src/pl/plpython/plpy_elog.h
 create mode 100644 src/pl/plpython/plpy_exec.c
 create mode 100644 src/pl/plpython/plpy_exec.h
 create mode 100644 src/pl/plpython/plpy_main.c
 create mode 100644 src/pl/plpython/plpy_main.h
 create mode 100644 src/pl/plpython/plpy_planobject.c
 create mode 100644 src/pl/plpython/plpy_planobject.h
 create mode 100644 src/pl/plpython/plpy_plpymodule.c
 create mode 100644 src/pl/plpython/plpy_plpymodule.h
 create mode 100644 src/pl/plpython/plpy_procedure.c
 create mode 100644 src/pl/plpython/plpy_procedure.h
 create mode 100644 src/pl/plpython/plpy_resultobject.c
 create mode 100644 src/pl/plpython/plpy_resultobject.h
 create mode 100644 src/pl/plpython/plpy_spi.c
 create mode 100644 src/pl/plpython/plpy_spi.h
 create mode 100644 src/pl/plpython/plpy_subxactobject.c
 create mode 100644 src/pl/plpython/plpy_subxactobject.h
 create mode 100644 src/pl/plpython/plpy_typeio.c
 create mode 100644 src/pl/plpython/plpy_typeio.h
 create mode 100644 src/pl/plpython/plpy_util.c
 create mode 100644 src/pl/plpython/plpy_util.h
 delete mode 100644 src/pl/plpython/plpython.c
 create mode 100644 src/pl/plpython/plpython.h

diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 1b1259feefb..12ce26e241f 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -38,7 +38,19 @@ rpathdir = $(python_libdir)
 
 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)
@@ -177,7 +189,7 @@ endif # can't build
 # 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 $< > $@
diff --git a/src/pl/plpython/nls.mk b/src/pl/plpython/nls.mk
index 27d5e7cea8f..94d406d0a98 100644
--- a/src/pl/plpython/nls.mk
+++ b/src/pl/plpython/nls.mk
@@ -1,7 +1,8 @@
 # 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 \
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
new file mode 100644
index 00000000000..48a7727b798
--- /dev/null
+++ b/src/pl/plpython/plpy_cursorobject.c
@@ -0,0 +1,492 @@
+/*
+ * 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;
+}
diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h
new file mode 100644
index 00000000000..706134ea2c7
--- /dev/null
+++ b/src/pl/plpython/plpy_cursorobject.h
@@ -0,0 +1,22 @@
+/*
+ * 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 */
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 00000000000..0ff55ac8bd8
--- /dev/null
+++ b/src/pl/plpython/plpy_elog.c
@@ -0,0 +1,428 @@
+/*
+ * 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);
+}
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 00000000000..eafc6e4e109
--- /dev/null
+++ b/src/pl/plpython/plpy_elog.h
@@ -0,0 +1,24 @@
+/*
+ * 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 */
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
new file mode 100644
index 00000000000..7724f3f0cd0
--- /dev/null
+++ b/src/pl/plpython/plpy_exec.c
@@ -0,0 +1,859 @@
+/*
+ * 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);
+	}
+}
diff --git a/src/pl/plpython/plpy_exec.h b/src/pl/plpython/plpy_exec.h
new file mode 100644
index 00000000000..86ceba13c45
--- /dev/null
+++ b/src/pl/plpython/plpy_exec.h
@@ -0,0 +1,13 @@
+/*
+ * 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 */
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
new file mode 100644
index 00000000000..03c03d1d7d9
--- /dev/null
+++ b/src/pl/plpython/plpy_main.c
@@ -0,0 +1,325 @@
+/*
+ * 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();
+	}
+	PG_END_TRY();
+
+	PLy_procedure_delete(&proc);
+
+	/* Pop the error context stack */
+	error_context_stack = plerrcontext.previous;
+
+	PLy_curr_procedure = save_curr_proc;
+
+	PG_RETURN_VOID();
+}
+
+#if PY_MAJOR_VERSION < 3
+Datum
+plpython2_inline_handler(PG_FUNCTION_ARGS)
+{
+	return plpython_inline_handler(fcinfo);
+}
+#endif   /* PY_MAJOR_VERSION < 3 */
+
+static bool PLy_procedure_is_trigger(Form_pg_proc procStruct)
+{
+	return (procStruct->prorettype == TRIGGEROID ||
+			(procStruct->prorettype == OPAQUEOID &&
+			 procStruct->pronargs == 0));
+}
+
+static void
+plpython_error_callback(void *arg)
+{
+	if (PLy_curr_procedure)
+		errcontext("PL/Python function \"%s\"",
+				   PLy_procedure_name(PLy_curr_procedure));
+}
+
+static void
+plpython_inline_error_callback(void *arg)
+{
+	errcontext("PL/Python anonymous code block");
+}
diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h
new file mode 100644
index 00000000000..a71aead1769
--- /dev/null
+++ b/src/pl/plpython/plpy_main.h
@@ -0,0 +1,13 @@
+/*
+ * src/pl/plpython/plpy_main.h
+ */
+
+#ifndef PLPY_MAIN_H
+#define PLPY_MAIN_H
+
+#include "plpy_procedure.h"
+
+/* the interpreter's globals dict */
+extern PyObject *PLy_interp_globals;
+
+#endif	/* PLPY_MAIN_H */
diff --git a/src/pl/plpython/plpy_planobject.c b/src/pl/plpython/plpy_planobject.c
new file mode 100644
index 00000000000..01b40d1d817
--- /dev/null
+++ b/src/pl/plpython/plpy_planobject.c
@@ -0,0 +1,128 @@
+/*
+ * the PLyPlan class
+ *
+ * src/pl/plpython/plpy_planobject.c
+ */
+
+#include "postgres.h"
+
+#include "plpython.h"
+
+#include "plpy_planobject.h"
+
+#include "plpy_elog.h"
+
+
+static void PLy_plan_dealloc(PyObject *);
+static PyObject *PLy_plan_status(PyObject *, PyObject *);
+
+static char PLy_plan_doc[] = {
+	"Store a PostgreSQL plan"
+};
+
+static PyMethodDef PLy_plan_methods[] = {
+	{"status", PLy_plan_status, METH_VARARGS, NULL},
+	{NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_PlanType = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"PLyPlan",					/* tp_name */
+	sizeof(PLyPlanObject),		/* tp_size */
+	0,							/* tp_itemsize */
+
+	/*
+	 * methods
+	 */
+	PLy_plan_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,	/* tp_flags */
+	PLy_plan_doc,				/* tp_doc */
+	0,							/* tp_traverse */
+	0,							/* tp_clear */
+	0,							/* tp_richcompare */
+	0,							/* tp_weaklistoffset */
+	0,							/* tp_iter */
+	0,							/* tp_iternext */
+	PLy_plan_methods,			/* tp_tpmethods */
+};
+
+void
+PLy_plan_init_type(void)
+{
+	if (PyType_Ready(&PLy_PlanType) < 0)
+		elog(ERROR, "could not initialize PLy_PlanType");
+}
+
+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->values = NULL;
+	ob->args = NULL;
+
+	return (PyObject *) ob;
+}
+
+bool
+is_PLyPlanObject(PyObject *ob)
+{
+	return ob->ob_type == &PLy_PlanType;
+}
+
+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->values)
+		PLy_free(ob->values);
+	if (ob->args)
+	{
+		int			i;
+
+		for (i = 0; i < ob->nargs; i++)
+			PLy_typeinfo_dealloc(&ob->args[i]);
+		PLy_free(ob->args);
+	}
+
+	arg->ob_type->tp_free(arg);
+}
+
+
+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); */
+	}
+	PLy_exception_set(PLy_exc_error, "plan.status takes no arguments");
+	return NULL;
+}
diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h
new file mode 100644
index 00000000000..959813a24c5
--- /dev/null
+++ b/src/pl/plpython/plpy_planobject.h
@@ -0,0 +1,26 @@
+/*
+ * src/pl/plpython/plpy_planobject.h
+ */
+
+#ifndef PLPY_PLANOBJECT_H
+#define PLPY_PLANOBJECT_H
+
+#include "executor/spi.h"
+#include "plpy_typeio.h"
+
+
+typedef struct PLyPlanObject
+{
+	PyObject_HEAD
+	SPIPlanPtr	plan;
+	int			nargs;
+	Oid		   *types;
+	Datum	   *values;
+	PLyTypeInfo *args;
+} PLyPlanObject;
+
+extern void PLy_plan_init_type(void);
+extern PyObject *PLy_plan_new(void);
+extern bool is_PLyPlanObject(PyObject *);
+
+#endif	/* PLPY_PLANOBJECT_H */
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
new file mode 100644
index 00000000000..e911107d9ad
--- /dev/null
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -0,0 +1,417 @@
+/*
+ * the plpy module
+ *
+ * src/pl/plpython/plpy_plpymodule.c
+ */
+
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+
+#include "plpython.h"
+
+#include "plpy_plpymodule.h"
+
+#include "plpy_cursorobject.h"
+#include "plpy_elog.h"
+#include "plpy_planobject.h"
+#include "plpy_resultobject.h"
+#include "plpy_spi.h"
+#include "plpy_subxactobject.h"
+
+
+HTAB *PLy_spi_exceptions = NULL;
+
+
+static void PLy_add_exceptions(PyObject *);
+static void PLy_generate_spi_exceptions(PyObject *, PyObject *);
+
+/* module functions */
+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 *);
+static PyObject *PLy_quote_literal(PyObject *, PyObject *);
+static PyObject *PLy_quote_nullable(PyObject *, PyObject *);
+static PyObject *PLy_quote_ident(PyObject *, PyObject *);
+
+
+/* A list of all known exceptions, generated from backend/utils/errcodes.txt */
+typedef struct ExceptionMap
+{
+	char	   *name;
+	char	   *classname;
+	int			sqlstate;
+} ExceptionMap;
+
+static const ExceptionMap exception_map[] = {
+#include "spiexceptions.h"
+	{NULL, NULL, 0}
+};
+
+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},
+
+	/*
+	 * escaping strings
+	 */
+	{"quote_literal", PLy_quote_literal, METH_VARARGS, NULL},
+	{"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
+	{"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
+
+	/*
+	 * create the subtransaction context manager
+	 */
+	{"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL},
+
+	/*
+	 * create a cursor
+	 */
+	{"cursor", PLy_cursor, METH_VARARGS, NULL},
+
+	{NULL, NULL, 0, NULL}
+};
+
+static PyMethodDef PLy_exc_methods[] = {
+	{NULL, NULL, 0, NULL}
+};
+
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef PLy_module = {
+	PyModuleDef_HEAD_INIT,		/* m_base */
+	"plpy",						/* m_name */
+	NULL,						/* m_doc */
+	-1,							/* m_size */
+	PLy_methods,				/* m_methods */
+};
+
+static PyModuleDef PLy_exc_module = {
+	PyModuleDef_HEAD_INIT,		/* m_base */
+	"spiexceptions",			/* m_name */
+	NULL,						/* m_doc */
+	-1,							/* m_size */
+	PLy_exc_methods,			/* m_methods */
+	NULL,						/* m_reload */
+	NULL,						/* m_traverse */
+	NULL,						/* m_clear */
+	NULL						/* m_free */
+};
+
+/*
+ * Must have external linkage, because PyMODINIT_FUNC does dllexport on
+ * Windows-like platforms.
+ */
+PyMODINIT_FUNC
+PyInit_plpy(void)
+{
+	PyObject   *m;
+
+	m = PyModule_Create(&PLy_module);
+	if (m == NULL)
+		return NULL;
+
+	PLy_add_exceptions(m);
+
+	return m;
+}
+#endif /* PY_MAJOR_VERSION >= 3 */
+
+void
+PLy_init_plpy(void)
+{
+	PyObject   *main_mod,
+			   *main_dict,
+			   *plpy_mod;
+#if PY_MAJOR_VERSION < 3
+	PyObject   *plpy;
+#endif
+
+	/*
+	 * initialize plpy module
+	 */
+	PLy_plan_init_type();
+	PLy_result_init_type();
+	PLy_subtransaction_init_type();
+	PLy_cursor_init_type();
+
+#if PY_MAJOR_VERSION >= 3
+	PyModule_Create(&PLy_module);
+	/* for Python 3 we initialized the exceptions in PyInit_plpy */
+#else
+	plpy = Py_InitModule("plpy", PLy_methods);
+	PLy_add_exceptions(plpy);
+#endif
+
+	/* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
+
+	/*
+	 * 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 initialize plpy");
+}
+
+static void
+PLy_add_exceptions(PyObject *plpy)
+{
+	PyObject   *excmod;
+	HASHCTL		hash_ctl;
+
+#if PY_MAJOR_VERSION < 3
+	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
+#else
+	excmod = PyModule_Create(&PLy_exc_module);
+#endif
+	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+		PLy_elog(ERROR, "could not add the spiexceptions module");
+
+	/*
+	 * XXX it appears that in some circumstances the reference count of the
+	 * spiexceptions module drops to zero causing a Python assert failure when
+	 * the garbage collector visits the module. This has been observed on the
+	 * buildfarm. To fix this, add an additional ref for the module here.
+	 *
+	 * This shouldn't cause a memory leak - we don't want this garbage
+	 * collected, and this function shouldn't be called more than once per
+	 * backend.
+	 */
+	Py_INCREF(excmod);
+
+	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);
+
+	Py_INCREF(PLy_exc_error);
+	PyModule_AddObject(plpy, "Error", PLy_exc_error);
+	Py_INCREF(PLy_exc_fatal);
+	PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
+	Py_INCREF(PLy_exc_spi_error);
+	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(int);
+	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+	hash_ctl.hash = tag_hash;
+	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
+									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
+
+	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
+}
+
+/*
+ * Add all the autogenerated exceptions as subclasses of SPIError
+ */
+static void
+PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+{
+	int			i;
+
+	for (i = 0; exception_map[i].name != NULL; i++)
+	{
+		bool		found;
+		PyObject   *exc;
+		PLyExceptionEntry *entry;
+		PyObject   *sqlstate;
+		PyObject   *dict = PyDict_New();
+
+		sqlstate = PyString_FromString(unpack_sql_state(exception_map[i].sqlstate));
+		PyDict_SetItemString(dict, "sqlstate", sqlstate);
+		Py_DECREF(sqlstate);
+		exc = PyErr_NewException(exception_map[i].name, base, dict);
+		PyModule_AddObject(mod, exception_map[i].classname, exc);
+		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+							HASH_ENTER, &found);
+		entry->exc = exc;
+		Assert(!found);
+	}
+}
+
+
+/*
+ * the python interface to the elog function
+ * don't confuse these with PLy_elog
+ */
+static PyObject *PLy_output(volatile int, PyObject *, PyObject *);
+
+PyObject *
+PLy_debug(PyObject *self, PyObject *args)
+{
+	return PLy_output(DEBUG2, self, args);
+}
+
+PyObject *
+PLy_log(PyObject *self, PyObject *args)
+{
+	return PLy_output(LOG, self, args);
+}
+
+PyObject *
+PLy_info(PyObject *self, PyObject *args)
+{
+	return PLy_output(INFO, self, args);
+}
+
+PyObject *
+PLy_notice(PyObject *self, PyObject *args)
+{
+	return PLy_output(NOTICE, self, args);
+}
+
+PyObject *
+PLy_warning(PyObject *self, PyObject *args)
+{
+	return PLy_output(WARNING, self, args);
+}
+
+PyObject *
+PLy_error(PyObject *self, PyObject *args)
+{
+	return PLy_output(ERROR, self, args);
+}
+
+PyObject *
+PLy_fatal(PyObject *self, PyObject *args)
+{
+	return PLy_output(FATAL, self, args);
+}
+
+PyObject *
+PLy_quote_literal(PyObject *self, PyObject *args)
+{
+	const char *str;
+	char	   *quoted;
+	PyObject   *ret;
+
+	if (!PyArg_ParseTuple(args, "s", &str))
+		return NULL;
+
+	quoted = quote_literal_cstr(str);
+	ret = PyString_FromString(quoted);
+	pfree(quoted);
+
+	return ret;
+}
+
+PyObject *
+PLy_quote_nullable(PyObject *self, PyObject *args)
+{
+	const char *str;
+	char	   *quoted;
+	PyObject   *ret;
+
+	if (!PyArg_ParseTuple(args, "z", &str))
+		return NULL;
+
+	if (str == NULL)
+		return PyString_FromString("NULL");
+
+	quoted = quote_literal_cstr(str);
+	ret = PyString_FromString(quoted);
+	pfree(quoted);
+
+	return ret;
+}
+
+PyObject *
+PLy_quote_ident(PyObject *self, PyObject *args)
+{
+	const char *str;
+	const char *quoted;
+	PyObject   *ret;
+
+	if (!PyArg_ParseTuple(args, "s", &str))
+		return NULL;
+
+	quoted = quote_identifier(str);
+	ret = PyString_FromString(quoted);
+
+	return ret;
+}
+
+static PyObject *
+PLy_output(volatile int level, PyObject *self, PyObject *args)
+{
+	PyObject   *volatile so;
+	char	   *volatile sv;
+	volatile MemoryContext oldcontext;
+
+	if (PyTuple_Size(args) == 1)
+	{
+		/*
+		 * Treat single argument specially to avoid undesirable ('tuple',)
+		 * decoration.
+		 */
+		PyObject   *o;
+
+		PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o);
+		so = PyObject_Str(o);
+	}
+	else
+		so = PyObject_Str(args);
+	if (so == NULL || ((sv = PyString_AsString(so)) == NULL))
+	{
+		level = ERROR;
+		sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
+	}
+
+	oldcontext = CurrentMemoryContext;
+	PG_TRY();
+	{
+		pg_verifymbstr(sv, strlen(sv), false);
+		elog(level, "%s", sv);
+	}
+	PG_CATCH();
+	{
+		ErrorData  *edata;
+
+		MemoryContextSwitchTo(oldcontext);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		/*
+		 * Note: If sv came from PyString_AsString(), it points into storage
+		 * owned by so.  So free so after using sv.
+		 */
+		Py_XDECREF(so);
+
+		/* Make Python raise the exception */
+		PLy_exception_set(PLy_exc_error, "%s", edata->message);
+		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;
+}
diff --git a/src/pl/plpython/plpy_plpymodule.h b/src/pl/plpython/plpy_plpymodule.h
new file mode 100644
index 00000000000..930ecfd1b17
--- /dev/null
+++ b/src/pl/plpython/plpy_plpymodule.h
@@ -0,0 +1,19 @@
+/*
+ * src/pl/plpython/plpy_plpymodule.h
+ */
+
+#ifndef PLPY_PLPYMODULE_H
+#define PLPY_PLPYMODULE_H
+
+#include "utils/hsearch.h"
+
+/* A hash table mapping sqlstates to exceptions, for speedy lookup */
+extern HTAB *PLy_spi_exceptions;
+
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC PyInit_plpy(void);
+#endif
+extern void PLy_init_plpy(void);
+
+#endif	/* PLPY_PLPYMODULE_H */
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
new file mode 100644
index 00000000000..b4f2abe262d
--- /dev/null
+++ b/src/pl/plpython/plpy_procedure.c
@@ -0,0 +1,531 @@
+/*
+ * Python procedure manipulation for plpython
+ *
+ * src/pl/plpython/plpy_procedure.c
+ */
+
+#include "postgres.h"
+
+#include "access/transam.h"
+#include "funcapi.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/hsearch.h"
+#include "utils/syscache.h"
+
+#include "plpython.h"
+
+#include "plpy_procedure.h"
+
+#include "plpy_elog.h"
+#include "plpy_main.h"
+
+
+PLyProcedure *PLy_curr_procedure = NULL;
+
+
+static HTAB *PLy_procedure_cache = NULL;
+static HTAB *PLy_trigger_cache = NULL;
+
+static PLyProcedure *PLy_procedure_create(HeapTuple, Oid, bool);
+static bool PLy_procedure_argument_valid(PLyTypeInfo *);
+static bool PLy_procedure_valid(PLyProcedure *, HeapTuple procTup);
+static char *PLy_procedure_munge_source(const char *, const char *);
+
+
+void
+init_procedure_caches(void)
+{
+	HASHCTL		hash_ctl;
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(Oid);
+	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
+	hash_ctl.hash = oid_hash;
+	PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
+									  HASH_ELEM | HASH_FUNCTION);
+
+	memset(&hash_ctl, 0, sizeof(hash_ctl));
+	hash_ctl.keysize = sizeof(Oid);
+	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
+	hash_ctl.hash = oid_hash;
+	PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
+									HASH_ELEM | HASH_FUNCTION);
+}
+
+/*
+ * 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
+ */
+char *
+PLy_procedure_name(PLyProcedure *proc)
+{
+	if (proc == NULL)
+		return "<unknown procedure>";
+	return proc->proname;
+}
+
+/*
+ * 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.
+ */
+PLyProcedure *
+PLy_procedure_get(Oid fn_oid, bool is_trigger)
+{
+	HeapTuple	procTup;
+	PLyProcedureEntry *volatile entry;
+	bool		found;
+
+	procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
+	if (!HeapTupleIsValid(procTup))
+		elog(ERROR, "cache lookup failed for function %u", fn_oid);
+
+	/* Look for the function in the corresponding cache */
+	if (is_trigger)
+		entry = hash_search(PLy_trigger_cache,
+							&fn_oid, HASH_ENTER, &found);
+	else
+		entry = hash_search(PLy_procedure_cache,
+							&fn_oid, HASH_ENTER, &found);
+
+	PG_TRY();
+	{
+		if (!found)
+		{
+			/* Haven't found it, create a new cache entry */
+			entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+		}
+		else if (!PLy_procedure_valid(entry->proc, procTup))
+		{
+			/* Found it, but it's invalid, free and reuse the cache entry */
+			PLy_procedure_delete(entry->proc);
+			PLy_free(entry->proc);
+			entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+		}
+		/* Found it and it's valid, it's fine to use it */
+	}
+	PG_CATCH();
+	{
+		/* Do not leave an uninitialised entry in the cache */
+		if (is_trigger)
+			hash_search(PLy_trigger_cache,
+						&fn_oid, HASH_REMOVE, NULL);
+		else
+			hash_search(PLy_procedure_cache,
+						&fn_oid, HASH_REMOVE, NULL);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	ReleaseSysCache(procTup);
+
+	return entry->proc;
+}
+
+/*
+ * Create a new PLyProcedure structure
+ */
+static PLyProcedure *
+PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
+{
+	char		procName[NAMEDATALEN + 256];
+	Form_pg_proc procStruct;
+	PLyProcedure *volatile proc;
+	char	   *volatile procSource = NULL;
+	Datum		prosrcdatum;
+	bool		isnull;
+	int			i,
+				rv;
+
+	procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+	rv = snprintf(procName, sizeof(procName),
+				  "__plpython_procedure_%s_%u",
+				  NameStr(procStruct->proname),
+				  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_tid = procTup->t_self;
+	/* 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 = NULL;
+	proc->is_setof = procStruct->proretset;
+	proc->setof = NULL;
+	proc->src = 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 (!is_trigger)
+		{
+			HeapTuple	rvTypeTup;
+			Form_pg_type rvTypeStruct;
+
+			rvTypeTup = SearchSysCache1(TYPEOID,
+								   ObjectIdGetDatum(procStruct->prorettype));
+			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 or record */
+			if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
+			{
+				if (procStruct->prorettype == TRIGGEROID)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("trigger functions can only be called as triggers")));
+				else if (procStruct->prorettype != VOIDOID &&
+						 procStruct->prorettype != RECORDOID)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						  errmsg("PL/Python functions cannot return type %s",
+								 format_type_be(procStruct->prorettype))));
+			}
+
+			if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
+				procStruct->prorettype == RECORDOID)
+			{
+				/*
+				 * Tuple: set up later, during first call to
+				 * PLy_function_handler
+				 */
+				proc->result.out.d.typoid = procStruct->prorettype;
+				proc->result.out.d.typmod = -1;
+				proc->result.is_rowtype = 2;
+			}
+			else
+			{
+				/* do the real work */
+				PLy_output_datum_func(&proc->result, rvTypeTup);
+			}
+
+			ReleaseSysCache(rvTypeTup);
+		}
+
+		/*
+		 * Now get information required for input conversion of the
+		 * procedure's arguments.  Note that we ignore output arguments here.
+		 * If the function returns record, those I/O functions will be set up
+		 * when the function is first called.
+		 */
+		if (procStruct->pronargs)
+		{
+			Oid		   *types;
+			char	  **names,
+					   *modes;
+			int			i,
+						pos,
+						total;
+
+			/* extract argument type info from the pg_proc tuple */
+			total = get_func_arg_info(procTup, &types, &names, &modes);
+
+			/* count number of in+inout args into proc->nargs */
+			if (modes == NULL)
+				proc->nargs = total;
+			else
+			{
+				/* proc->nargs was initialized to 0 above */
+				for (i = 0; i < total; i++)
+				{
+					if (modes[i] != PROARGMODE_OUT &&
+						modes[i] != PROARGMODE_TABLE)
+						(proc->nargs)++;
+				}
+			}
+
+			proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs);
+			for (i = pos = 0; i < total; i++)
+			{
+				HeapTuple	argTypeTup;
+				Form_pg_type argTypeStruct;
+
+				if (modes &&
+					(modes[i] == PROARGMODE_OUT ||
+					 modes[i] == PROARGMODE_TABLE))
+					continue;	/* skip OUT arguments */
+
+				Assert(types[i] == procStruct->proargtypes.values[pos]);
+
+				argTypeTup = SearchSysCache1(TYPEOID,
+											 ObjectIdGetDatum(types[i]));
+				if (!HeapTupleIsValid(argTypeTup))
+					elog(ERROR, "cache lookup failed for type %u", types[i]);
+				argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
+
+				/* check argument type is OK, set up I/O function info */
+				switch (argTypeStruct->typtype)
+				{
+					case TYPTYPE_PSEUDO:
+						/* Disallow pseudotype argument */
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						  errmsg("PL/Python functions cannot accept type %s",
+								 format_type_be(types[i]))));
+						break;
+					case TYPTYPE_COMPOSITE:
+						/* we'll set IO funcs at first call */
+						proc->args[pos].is_rowtype = 2;
+						break;
+					default:
+						PLy_input_datum_func(&(proc->args[pos]),
+											 types[i],
+											 argTypeTup);
+						break;
+				}
+
+				/* get argument name */
+				proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL;
+
+				ReleaseSysCache(argTypeTup);
+
+				pos++;
+			}
+		}
+
+		/*
+		 * get the text of the function.
+		 */
+		prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+									  Anum_pg_proc_prosrc, &isnull);
+		if (isnull)
+			elog(ERROR, "null prosrc");
+		procSource = TextDatumGetCString(prosrcdatum);
+
+		PLy_procedure_compile(proc, procSource);
+
+		pfree(procSource);
+		procSource = NULL;
+	}
+	PG_CATCH();
+	{
+		PLy_procedure_delete(proc);
+		if (procSource)
+			pfree(procSource);
+
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return proc;
+}
+
+/*
+ * Insert the procedure into the Python interpreter
+ */
+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);
+	/* Save the mangled source for later inclusion in tracebacks */
+	proc->src = PLy_strdup(msrc);
+	crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
+	pfree(msrc);
+
+	if (crv != NULL)
+	{
+		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, "<string>", Py_eval_input);
+		if (proc->code != NULL)
+			return;
+	}
+
+	if (proc->proname)
+		PLy_elog(ERROR, "could not compile PL/Python function \"%s\"",
+				 proc->proname);
+	else
+		PLy_elog(ERROR, "could not compile anonymous PL/Python code block");
+}
+
+void
+PLy_procedure_delete(PLyProcedure *proc)
+{
+	int			i;
+
+	Py_XDECREF(proc->code);
+	Py_XDECREF(proc->statics);
+	Py_XDECREF(proc->globals);
+	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->src)
+		PLy_free(proc->src);
+	if (proc->argnames)
+		PLy_free(proc->argnames);
+}
+
+/*
+ * Check if our cached information about a datatype is still valid
+ */
+static bool
+PLy_procedure_argument_valid(PLyTypeInfo *arg)
+{
+	HeapTuple	relTup;
+	bool		valid;
+
+	/* Nothing to cache unless type is composite */
+	if (arg->is_rowtype != 1)
+		return true;
+
+	/*
+	 * Zero typ_relid means that we got called on an output argument of a
+	 * function returning a unnamed record type; the info for it can't change.
+	 */
+	if (!OidIsValid(arg->typ_relid))
+		return true;
+
+	/* Else we should have some cached data */
+	Assert(TransactionIdIsValid(arg->typrel_xmin));
+	Assert(ItemPointerIsValid(&arg->typrel_tid));
+
+	/* Get the pg_class tuple for the data type */
+	relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
+	if (!HeapTupleIsValid(relTup))
+		elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
+
+	/* If it has changed, the cached data is not valid */
+	valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
+			 ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
+
+	ReleaseSysCache(relTup);
+
+	return valid;
+}
+
+/*
+ * Decide whether a cached PLyProcedure struct is still valid
+ */
+static bool
+PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
+{
+	int			i;
+	bool		valid;
+
+	Assert(proc != NULL);
+
+	/* If the pg_proc tuple has changed, it's not valid */
+	if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+		  ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
+		return false;
+
+	/* Else check the input argument datatypes */
+	valid = true;
+	for (i = 0; i < proc->nargs; i++)
+	{
+		valid = PLy_procedure_argument_valid(&proc->args[i]);
+
+		/* Short-circuit on first changed argument */
+		if (!valid)
+			break;
+	}
+
+	/* if the output type is composite, it might have changed */
+	if (valid)
+		valid = PLy_procedure_argument_valid(&proc->result);
+
+	return valid;
+}
+
+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 = palloc(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;
+}
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
new file mode 100644
index 00000000000..632b975cc19
--- /dev/null
+++ b/src/pl/plpython/plpy_procedure.h
@@ -0,0 +1,52 @@
+/*
+ * src/pl/plpython/plpy_procedure.h
+ */
+
+#ifndef PLPY_PROCEDURE_H
+#define PLPY_PROCEDURE_H
+
+#include "plpy_typeio.h"
+
+
+extern void init_procedure_caches(void);
+
+
+/* cached procedure data */
+typedef struct PLyProcedure
+{
+	char	   *proname;		/* SQL name of procedure */
+	char	   *pyname;			/* Python name of procedure */
+	TransactionId fn_xmin;
+	ItemPointerData fn_tid;
+	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	   *src;			/* textual procedure code, after mangling */
+	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 */
+} PLyProcedure;
+
+/* the procedure cache entry */
+typedef struct PLyProcedureEntry
+{
+	Oid			fn_oid;			/* hash key */
+	PLyProcedure *proc;
+} PLyProcedureEntry;
+
+/* PLyProcedure manipulation */
+extern char *PLy_procedure_name(PLyProcedure *);
+extern PLyProcedure *PLy_procedure_get(Oid, bool);
+extern void PLy_procedure_compile(PLyProcedure *, const char *);
+extern void PLy_procedure_delete(PLyProcedure *);
+
+
+/* currently active plpython function */
+extern PLyProcedure *PLy_curr_procedure;
+
+#endif	/* PLPY_PROCEDURE_H */
diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c
new file mode 100644
index 00000000000..e1b89260ff8
--- /dev/null
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -0,0 +1,180 @@
+/*
+ * the PLyResult class
+ *
+ * src/pl/plpython/plpy_resultobject.c
+ */
+
+#include "postgres.h"
+
+#include "plpython.h"
+
+#include "plpy_resultobject.h"
+
+
+static void PLy_result_dealloc(PyObject *);
+static PyObject *PLy_result_nrows(PyObject *, PyObject *);
+static PyObject *PLy_result_status(PyObject *, PyObject *);
+static Py_ssize_t PLy_result_length(PyObject *);
+static PyObject *PLy_result_item(PyObject *, Py_ssize_t);
+static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t);
+static int	PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *);
+static int	PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);
+
+static char PLy_result_doc[] = {
+	"Results of a PostgreSQL query"
+};
+
+static PySequenceMethods PLy_result_as_sequence = {
+	PLy_result_length,			/* sq_length */
+	NULL,						/* sq_concat */
+	NULL,						/* sq_repeat */
+	PLy_result_item,			/* sq_item */
+	PLy_result_slice,			/* sq_slice */
+	PLy_result_ass_item,		/* sq_ass_item */
+	PLy_result_ass_slice,		/* sq_ass_slice */
+};
+
+static PyMethodDef PLy_result_methods[] = {
+	{"nrows", PLy_result_nrows, METH_VARARGS, NULL},
+	{"status", PLy_result_status, METH_VARARGS, NULL},
+	{NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_ResultType = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"PLyResult",				/* tp_name */
+	sizeof(PLyResultObject),	/* tp_size */
+	0,							/* tp_itemsize */
+
+	/*
+	 * methods
+	 */
+	PLy_result_dealloc,			/* tp_dealloc */
+	0,							/* tp_print */
+	0,							/* 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 */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
+	PLy_result_doc,				/* tp_doc */
+	0,							/* tp_traverse */
+	0,							/* tp_clear */
+	0,							/* tp_richcompare */
+	0,							/* tp_weaklistoffset */
+	0,							/* tp_iter */
+	0,							/* tp_iternext */
+	PLy_result_methods,			/* tp_tpmethods */
+};
+
+void
+PLy_result_init_type(void)
+{
+	if (PyType_Ready(&PLy_ResultType) < 0)
+		elog(ERROR, "could not initialize PLy_ResultType");
+}
+
+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);
+
+	arg->ob_type->tp_free(arg);
+}
+
+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 Py_ssize_t
+PLy_result_length(PyObject *arg)
+{
+	PLyResultObject *ob = (PLyResultObject *) arg;
+
+	return PyList_Size(ob->rows);
+}
+
+static PyObject *
+PLy_result_item(PyObject *arg, Py_ssize_t 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, Py_ssize_t 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, Py_ssize_t lidx, Py_ssize_t hidx)
+{
+	PLyResultObject *ob = (PLyResultObject *) arg;
+
+	return PyList_GetSlice(ob->rows, lidx, hidx);
+}
+
+static int
+PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice)
+{
+	int			rv;
+	PLyResultObject *ob = (PLyResultObject *) arg;
+
+	rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
+	return rv;
+}
diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h
new file mode 100644
index 00000000000..719828a3efb
--- /dev/null
+++ b/src/pl/plpython/plpy_resultobject.h
@@ -0,0 +1,20 @@
+/*
+ * src/pl/plpython/plpy_resultobject.h
+ */
+
+#ifndef PLPY_RESULTOBJECT_H
+#define PLPY_RESULTOBJECT_H
+
+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;
+
+extern void PLy_result_init_type(void);
+extern PyObject *PLy_result_new(void);
+
+#endif	/* PLPY_RESULTOBJECT_H */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index 00000000000..5e3099ee5bc
--- /dev/null
+++ b/src/pl/plpython/plpy_spi.c
@@ -0,0 +1,559 @@
+/*
+ * interface to SPI functions
+ *
+ * src/pl/plpython/plpy_spi.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "catalog/pg_type.h"
+#include "executor/spi_priv.h"
+#include "mb/pg_wchar.h"
+#include "parser/parse_type.h"
+#include "utils/syscache.h"
+
+#include "plpython.h"
+
+#include "plpy_spi.h"
+
+#include "plpy_elog.h"
+#include "plpy_planobject.h"
+#include "plpy_plpymodule.h"
+#include "plpy_procedure.h"
+#include "plpy_resultobject.h"
+
+
+static PyObject *PLy_spi_execute_query(char *, long );
+static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long);
+static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int);
+static void PLy_spi_exception_set(PyObject *, ErrorData *);
+
+
+/* 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;
+}
+
+/* execute(query="select * from foo", limit=5)
+ * execute(plan=plan, values=(foo, bar), limit=5)
+ */
+PyObject *
+PLy_spi_execute(PyObject *self, PyObject *args)
+{
+	char	   *query;
+	PyObject   *plan;
+	PyObject   *list = NULL;
+	long		limit = 0;
+
+	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);
+
+	PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan");
+	return NULL;
+}
+
+static PyObject *
+PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
+{
+	volatile int nargs;
+	int			i,
+				rv;
+	PLyPlanObject *plan;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+	PyObject   *ret;
+
+	if (list != NULL)
+	{
+		if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list))
+		{
+			PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument");
+			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, "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;
+	}
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		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(list, 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';
+			}
+		}
+
+		rv = SPI_execute_plan(plan->plan, plan->values, nulls,
+							  PLy_curr_procedure->fn_readonly, limit);
+		ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
+
+		if (nargs > 0)
+			pfree(nulls);
+
+		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);
+			}
+		}
+
+		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);
+		}
+	}
+
+	if (rv < 0)
+	{
+		PLy_exception_set(PLy_exc_spi_error,
+						  "SPI_execute_plan failed: %s",
+						  SPI_result_code_string(rv));
+		return NULL;
+	}
+
+	return ret;
+}
+
+static PyObject *
+PLy_spi_execute_query(char *query, long limit)
+{
+	int			rv;
+	volatile MemoryContext oldcontext;
+	volatile ResourceOwner oldowner;
+	PyObject   *ret;
+
+	oldcontext = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+	PG_TRY();
+	{
+		pg_verifymbstr(query, strlen(query), false);
+		rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
+		ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
+
+		PLy_spi_subtransaction_commit(oldcontext, oldowner);
+	}
+	PG_CATCH();
+	{
+		PLy_spi_subtransaction_abort(oldcontext, oldowner);
+		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 ret;
+}
+
+static PyObject *
+PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
+{
+	PLyResultObject *result;
+	volatile 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);
+				}
+			}
+		}
+		PG_CATCH();
+		{
+			MemoryContextSwitchTo(oldcontext);
+			if (!PyErr_Occurred())
+				PLy_exception_set(PLy_exc_error,
+					   "unrecognized error in PLy_spi_execute_fetch_result");
+			PLy_typeinfo_dealloc(&args);
+			SPI_freetuptable(tuptable);
+			Py_DECREF(result);
+			return NULL;
+		}
+		PG_END_TRY();
+
+		PLy_typeinfo_dealloc(&args);
+		SPI_freetuptable(tuptable);
+	}
+
+	return (PyObject *) result;
+}
+
+/*
+ * Utilities for running SPI functions in subtransactions.
+ *
+ * Usage:
+ *
+ *  MemoryContext oldcontext = CurrentMemoryContext;
+ *  ResourceOwner oldowner = CurrentResourceOwner;
+ *
+ *  PLy_spi_subtransaction_begin(oldcontext, oldowner);
+ *  PG_TRY();
+ *  {
+ *      <call SPI functions>
+ *      PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ *  }
+ *  PG_CATCH();
+ *  {
+ *      <do cleanup>
+ *      PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ *      return NULL;
+ *  }
+ *  PG_END_TRY();
+ *
+ * These utilities take care of restoring connection to the SPI manager and
+ * setting a Python exception in case of an abort.
+ */
+void
+PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	BeginInternalSubTransaction(NULL);
+	/* Want to run inside function's memory context */
+	MemoryContextSwitchTo(oldcontext);
+}
+
+void
+PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	/* Commit the inner transaction, return to outer xact context */
+	ReleaseCurrentSubTransaction();
+	MemoryContextSwitchTo(oldcontext);
+	CurrentResourceOwner = oldowner;
+
+	/*
+	 * AtEOSubXact_SPI() should not have popped any SPI context, but just
+	 * in case it did, make sure we remain connected.
+	 */
+	SPI_restore_connection();
+}
+
+void
+PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+	ErrorData  *edata;
+	PLyExceptionEntry *entry;
+	PyObject   *exc;
+
+	/* Save error info */
+	MemoryContextSwitchTo(oldcontext);
+	edata = CopyErrorData();
+	FlushErrorState();
+
+	/* Abort the inner transaction */
+	RollbackAndReleaseCurrentSubTransaction();
+	MemoryContextSwitchTo(oldcontext);
+	CurrentResourceOwner = oldowner;
+
+	/*
+	 * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will have
+	 * left us in a disconnected state.  We need this hack to return to
+	 * connected state.
+	 */
+	SPI_restore_connection();
+
+	/* Look up the correct exception */
+	entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+						HASH_FIND, NULL);
+	/* We really should find it, but just in case have a fallback */
+	Assert(entry != NULL);
+	exc = entry ? entry->exc : PLy_exc_spi_error;
+	/* Make Python raise the exception */
+	PLy_spi_exception_set(exc, edata);
+	FreeErrorData(edata);
+}
+
+/*
+ * Raise a SPIError, passing in it more error details, like the
+ * internal query and error position.
+ */
+static void
+PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
+{
+	PyObject   *args = NULL;
+	PyObject   *spierror = NULL;
+	PyObject   *spidata = NULL;
+
+	args = Py_BuildValue("(s)", edata->message);
+	if (!args)
+		goto failure;
+
+	/* create a new SPI exception with the error message as the parameter */
+	spierror = PyObject_CallObject(excclass, args);
+	if (!spierror)
+		goto failure;
+
+	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
+							edata->internalquery, edata->internalpos);
+	if (!spidata)
+		goto failure;
+
+	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
+		goto failure;
+
+	PyErr_SetObject(excclass, spierror);
+
+	Py_DECREF(args);
+	Py_DECREF(spierror);
+	Py_DECREF(spidata);
+	return;
+
+failure:
+	Py_XDECREF(args);
+	Py_XDECREF(spierror);
+	Py_XDECREF(spidata);
+	elog(ERROR, "could not convert SPI error to Python exception");
+}
diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h
new file mode 100644
index 00000000000..c59482a3f0f
--- /dev/null
+++ b/src/pl/plpython/plpy_spi.h
@@ -0,0 +1,25 @@
+/*
+ * src/pl/plpython/plpy_spi.h
+ */
+
+#ifndef PLPY_SPI_H
+#define PLPY_SPI_H
+
+#include "utils/palloc.h"
+#include "utils/resowner.h"
+
+extern PyObject *PLy_spi_prepare(PyObject *, PyObject *);
+extern PyObject *PLy_spi_execute(PyObject *, PyObject *);
+
+typedef struct PLyExceptionEntry
+{
+	int			sqlstate;		/* hash key, must be first */
+	PyObject   *exc;			/* corresponding exception */
+} PLyExceptionEntry;
+
+/* handling of SPI operations inside subtransactions */
+extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner);
+extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner);
+extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner);
+
+#endif	/* PLPY_SPI_H */
diff --git a/src/pl/plpython/plpy_subxactobject.c b/src/pl/plpython/plpy_subxactobject.c
new file mode 100644
index 00000000000..6c3cc69adfa
--- /dev/null
+++ b/src/pl/plpython/plpy_subxactobject.c
@@ -0,0 +1,217 @@
+/*
+ * the PLySubtransaction class
+ *
+ * src/pl/plpython/plpy_subxactobject.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "executor/spi.h"
+
+#include "plpython.h"
+
+#include "plpy_subxactobject.h"
+
+#include "plpy_elog.h"
+
+
+List *explicit_subtransactions = NIL;
+
+
+static void PLy_subtransaction_dealloc(PyObject *);
+static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *);
+static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *);
+
+static char PLy_subtransaction_doc[] = {
+	"PostgreSQL subtransaction context manager"
+};
+
+static PyMethodDef PLy_subtransaction_methods[] = {
+	{"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL},
+	{"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL},
+	/* user-friendly names for Python <2.6 */
+	{"enter", PLy_subtransaction_enter, METH_VARARGS, NULL},
+	{"exit", PLy_subtransaction_exit, METH_VARARGS, NULL},
+	{NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_SubtransactionType = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"PLySubtransaction",		/* tp_name */
+	sizeof(PLySubtransactionObject),	/* tp_size */
+	0,							/* tp_itemsize */
+
+	/*
+	 * methods
+	 */
+	PLy_subtransaction_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,	/* tp_flags */
+	PLy_subtransaction_doc,		/* tp_doc */
+	0,							/* tp_traverse */
+	0,							/* tp_clear */
+	0,							/* tp_richcompare */
+	0,							/* tp_weaklistoffset */
+	0,							/* tp_iter */
+	0,							/* tp_iternext */
+	PLy_subtransaction_methods, /* tp_tpmethods */
+};
+
+
+void
+PLy_subtransaction_init_type(void)
+{
+	if (PyType_Ready(&PLy_SubtransactionType) < 0)
+		elog(ERROR, "could not initialize PLy_SubtransactionType");
+}
+
+/* s = plpy.subtransaction() */
+PyObject *
+PLy_subtransaction_new(PyObject *self, PyObject *unused)
+{
+	PLySubtransactionObject *ob;
+
+	ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType);
+
+	if (ob == NULL)
+		return NULL;
+
+	ob->started = false;
+	ob->exited = false;
+
+	return (PyObject *) ob;
+}
+
+/* Python requires a dealloc function to be defined */
+static void
+PLy_subtransaction_dealloc(PyObject *subxact)
+{
+}
+
+/*
+ * subxact.__enter__() or subxact.enter()
+ *
+ * Start an explicit subtransaction.  SPI calls within an explicit
+ * subtransaction will not start another one, so you can atomically
+ * execute many SPI calls and still get a controllable exception if
+ * one of them fails.
+ */
+static PyObject *
+PLy_subtransaction_enter(PyObject *self, PyObject *unused)
+{
+	PLySubtransactionData *subxactdata;
+	MemoryContext oldcontext;
+	PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
+
+	if (subxact->started)
+	{
+		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered");
+		return NULL;
+	}
+
+	if (subxact->exited)
+	{
+		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
+		return NULL;
+	}
+
+	subxact->started = true;
+	oldcontext = CurrentMemoryContext;
+
+	subxactdata = PLy_malloc(sizeof(*subxactdata));
+	subxactdata->oldcontext = oldcontext;
+	subxactdata->oldowner = CurrentResourceOwner;
+
+	BeginInternalSubTransaction(NULL);
+	/* Do not want to leave the previous memory context */
+	MemoryContextSwitchTo(oldcontext);
+
+	explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
+
+	Py_INCREF(self);
+	return self;
+}
+
+/*
+ * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb)
+ *
+ * Exit an explicit subtransaction. exc_type is an exception type, exc
+ * is the exception object, tb is the traceback.  If exc_type is None,
+ * commit the subtransactiony, if not abort it.
+ *
+ * The method signature is chosen to allow subtransaction objects to
+ * be used as context managers as described in
+ * <http://www.python.org/dev/peps/pep-0343/>.
+ */
+static PyObject *
+PLy_subtransaction_exit(PyObject *self, PyObject *args)
+{
+	PyObject   *type;
+	PyObject   *value;
+	PyObject   *traceback;
+	PLySubtransactionData *subxactdata;
+	PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
+
+	if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback))
+		return NULL;
+
+	if (!subxact->started)
+	{
+		PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered");
+		return NULL;
+	}
+
+	if (subxact->exited)
+	{
+		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
+		return NULL;
+	}
+
+	if (explicit_subtransactions == NIL)
+	{
+		PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from");
+		return NULL;
+	}
+
+	subxact->exited = true;
+
+	if (type != Py_None)
+	{
+		/* Abort the inner transaction */
+		RollbackAndReleaseCurrentSubTransaction();
+	}
+	else
+	{
+		ReleaseCurrentSubTransaction();
+	}
+
+	subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions);
+	explicit_subtransactions = list_delete_first(explicit_subtransactions);
+
+	MemoryContextSwitchTo(subxactdata->oldcontext);
+	CurrentResourceOwner = subxactdata->oldowner;
+	PLy_free(subxactdata);
+
+	/*
+	 * AtEOSubXact_SPI() should not have popped any SPI context, but just in
+	 * case it did, make sure we remain connected.
+	 */
+	SPI_restore_connection();
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
diff --git a/src/pl/plpython/plpy_subxactobject.h b/src/pl/plpython/plpy_subxactobject.h
new file mode 100644
index 00000000000..0db8aa9f3fa
--- /dev/null
+++ b/src/pl/plpython/plpy_subxactobject.h
@@ -0,0 +1,29 @@
+/*
+ * src/pl/plpython/plpy_subxactobject.h
+ */
+
+#ifndef PLPY_SUBXACTOBJECT
+#define PLPY_SUBXACTOBJECT
+
+/* a list of nested explicit subtransactions */
+extern List *explicit_subtransactions;
+
+
+typedef struct PLySubtransactionObject
+{
+	PyObject_HEAD
+	bool		started;
+	bool		exited;
+} PLySubtransactionObject;
+
+/* explicit subtransaction data */
+typedef struct PLySubtransactionData
+{
+	MemoryContext oldcontext;
+	ResourceOwner oldowner;
+} PLySubtransactionData;
+
+extern void PLy_subtransaction_init_type(void);
+extern PyObject *PLy_subtransaction_new(PyObject *, PyObject *);
+
+#endif	/* PLPY_SUBXACTOBJECT */
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
new file mode 100644
index 00000000000..cd6a46d8da6
--- /dev/null
+++ b/src/pl/plpython/plpy_typeio.c
@@ -0,0 +1,1038 @@
+/*
+ * transforming Datums to Python objects and vice versa
+ *
+ * src/pl/plpython/plpy_typeio.c
+ */
+
+#include "postgres.h"
+
+#include "access/transam.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "parser/parse_type.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+#include "plpython.h"
+
+#include "plpy_typeio.h"
+
+#include "plpy_elog.h"
+
+
+/* I/O function caching */
+static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple);
+static void PLy_output_datum_func2(PLyObToDatum *, HeapTuple);
+
+/* conversion from Datums to Python objects */
+static PyObject *PLyBool_FromBool(PLyDatumToOb *, Datum);
+static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *, Datum);
+static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *, Datum);
+static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *, Datum);
+static PyObject *PLyInt_FromInt16(PLyDatumToOb *, Datum);
+static PyObject *PLyInt_FromInt32(PLyDatumToOb *, Datum);
+static PyObject *PLyLong_FromInt64(PLyDatumToOb *, Datum);
+static PyObject *PLyBytes_FromBytea(PLyDatumToOb *, Datum);
+static PyObject *PLyString_FromDatum(PLyDatumToOb *, Datum);
+static PyObject *PLyList_FromArray(PLyDatumToOb *, Datum);
+
+/* conversion from Python objects to Datums */
+static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *);
+static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *);
+static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *);
+static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *);
+static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *);
+
+/* conversion from Python objects to heap tuples (used by triggers and SRFs) */
+static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
+static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
+static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
+
+/* make allocations in the TopMemoryContext */
+static void perm_fmgr_info(Oid, FmgrInfo *);
+
+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;
+	arg->typ_relid = InvalidOid;
+	arg->typrel_xmin = InvalidTransactionId;
+	ItemPointerSetInvalid(&arg->typrel_tid);
+}
+
+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);
+	}
+}
+
+/*
+ * Conversion functions.  Remember output from Python is input to
+ * PostgreSQL, and vice versa.
+ */
+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);
+}
+
+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);
+}
+
+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;
+
+	if (arg->in.r.natts != desc->natts)
+	{
+		if (arg->in.r.atts)
+			PLy_free(arg->in.r.atts);
+		arg->in.r.natts = desc->natts;
+		arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
+	}
+
+	/* Can this be an unnamed tuple? If not, then an Assert would be enough */
+	if (desc->tdtypmod != -1)
+		elog(ERROR, "received unnamed record type as input");
+
+	Assert(OidIsValid(desc->tdtypeid));
+
+	/*
+	 * RECORDOID means we got called to create input functions for a tuple
+	 * fetched by plpy.execute or for an anonymous record type
+	 */
+	if (desc->tdtypeid != RECORDOID)
+	{
+		HeapTuple	relTup;
+
+		/* Get the pg_class tuple corresponding to the type of the input */
+		arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
+		relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
+		if (!HeapTupleIsValid(relTup))
+			elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
+
+		/* Remember XMIN and TID for later validation if cache is still OK */
+		arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
+		arg->typrel_tid = relTup->t_self;
+
+		ReleaseSysCache(relTup);
+	}
+
+	for (i = 0; i < desc->natts; i++)
+	{
+		HeapTuple	typeTup;
+
+		if (desc->attrs[i]->attisdropped)
+			continue;
+
+		if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid)
+			continue;			/* already set up this entry */
+
+		typeTup = SearchSysCache1(TYPEOID,
+								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
+		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);
+	}
+}
+
+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;
+
+	if (arg->out.r.natts != desc->natts)
+	{
+		if (arg->out.r.atts)
+			PLy_free(arg->out.r.atts);
+		arg->out.r.natts = desc->natts;
+		arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
+	}
+
+	Assert(OidIsValid(desc->tdtypeid));
+
+	/*
+	 * RECORDOID means we got called to create output functions for an
+	 * anonymous record type
+	 */
+	if (desc->tdtypeid != RECORDOID)
+	{
+		HeapTuple	relTup;
+
+		/* Get the pg_class tuple corresponding to the type of the output */
+		arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
+		relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
+		if (!HeapTupleIsValid(relTup))
+			elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
+
+		/* Remember XMIN and TID for later validation if cache is still OK */
+		arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
+		arg->typrel_tid = relTup->t_self;
+
+		ReleaseSysCache(relTup);
+	}
+
+	for (i = 0; i < desc->natts; i++)
+	{
+		HeapTuple	typeTup;
+
+		if (desc->attrs[i]->attisdropped)
+			continue;
+
+		if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid)
+			continue;			/* already set up this entry */
+
+		typeTup = SearchSysCache1(TYPEOID,
+								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
+		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);
+	}
+}
+
+void
+PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
+{
+	/*
+	 * If the output record functions are already set, we just have to check
+	 * if the record descriptor has not changed
+	 */
+	if ((arg->is_rowtype == 1) &&
+		(arg->out.d.typmod != -1) &&
+		(arg->out.d.typmod == desc->tdtypmod))
+		return;
+
+	/* bless the record to make it known to the typcache lookup code */
+	BlessTupleDesc(desc);
+	/* save the freshly generated typmod */
+	arg->out.d.typmod = desc->tdtypmod;
+	/* proceed with normal I/O function caching */
+	PLy_output_tuple_funcs(arg, desc);
+
+	/*
+	 * it should change is_rowtype to 1, so we won't go through this again
+	 * unless the the output record description changes
+	 */
+	Assert(arg->is_rowtype == 1);
+}
+
+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 new dictionary");
+
+	PG_TRY();
+	{
+		for (i = 0; i < info->in.r.natts; i++)
+		{
+			char	   *key;
+			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
+			{
+				value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
+				PyDict_SetItemString(dict, key, value);
+				Py_DECREF(value);
+			}
+		}
+	}
+	PG_CATCH();
+	{
+		Py_DECREF(dict);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return dict;
+}
+
+/*
+ *	Convert a Python object to a PostgreSQL tuple, using all supported
+ *	conversion methods: tuple as a sequence, as a mapping or as an object that
+ *	has __getattr__ support.
+ */
+HeapTuple
+PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
+{
+	HeapTuple	tuple;
+
+	if (PySequence_Check(plrv))
+		/* composite type as sequence (tuple, list etc) */
+		tuple = PLySequence_ToTuple(info, desc, plrv);
+	else if (PyMapping_Check(plrv))
+		/* composite type as mapping (currently only dict) */
+		tuple = PLyMapping_ToTuple(info, desc, plrv);
+	else
+		/* returned as smth, must provide method __getattr__(name) */
+		tuple = PLyGenericObject_ToTuple(info, desc, plrv);
+
+	return tuple;
+}
+
+static void
+PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
+{
+	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+	Oid			element_type;
+
+	perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
+	arg->typoid = HeapTupleGetOid(typeTup);
+	arg->typmod = -1;
+	arg->typioparam = getTypeIOParam(typeTup);
+	arg->typbyval = typeStruct->typbyval;
+
+	element_type = get_element_type(arg->typoid);
+
+	/*
+	 * Select a conversion function to convert Python objects to PostgreSQL
+	 * datums.	Most data types can go through the generic function.
+	 */
+	switch (getBaseType(element_type ? element_type : arg->typoid))
+	{
+		case BOOLOID:
+			arg->func = PLyObject_ToBool;
+			break;
+		case BYTEAOID:
+			arg->func = PLyObject_ToBytea;
+			break;
+		default:
+			arg->func = PLyObject_ToDatum;
+			break;
+	}
+
+	/* Composite types need their own input routine, though */
+	if (typeStruct->typtype == TYPTYPE_COMPOSITE)
+	{
+		arg->func = PLyObject_ToComposite;
+	}
+
+	if (element_type)
+	{
+		char		dummy_delim;
+		Oid			funcid;
+
+		if (type_is_rowtype(element_type))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("PL/Python functions cannot return type %s",
+							format_type_be(arg->typoid)),
+					 errdetail("PL/Python does not support conversion to arrays of row types.")));
+
+		arg->elm = PLy_malloc0(sizeof(*arg->elm));
+		arg->elm->func = arg->func;
+		arg->func = PLySequence_ToArray;
+
+		arg->elm->typoid = element_type;
+		arg->elm->typmod = -1;
+		get_type_io_data(element_type, IOFunc_input,
+						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
+						 &arg->elm->typioparam, &funcid);
+		perm_fmgr_info(funcid, &arg->elm->typfunc);
+	}
+}
+
+static void
+PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
+{
+	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+	Oid			element_type = get_element_type(typeOid);
+
+	/* Get the type's conversion information */
+	perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
+	arg->typoid = HeapTupleGetOid(typeTup);
+	arg->typmod = -1;
+	arg->typioparam = getTypeIOParam(typeTup);
+	arg->typbyval = typeStruct->typbyval;
+	arg->typlen = typeStruct->typlen;
+	arg->typalign = typeStruct->typalign;
+
+	/* Determine which kind of Python object we will convert to */
+	switch (getBaseType(element_type ? element_type : typeOid))
+	{
+		case BOOLOID:
+			arg->func = PLyBool_FromBool;
+			break;
+		case FLOAT4OID:
+			arg->func = PLyFloat_FromFloat4;
+			break;
+		case FLOAT8OID:
+			arg->func = PLyFloat_FromFloat8;
+			break;
+		case NUMERICOID:
+			arg->func = PLyFloat_FromNumeric;
+			break;
+		case INT2OID:
+			arg->func = PLyInt_FromInt16;
+			break;
+		case INT4OID:
+			arg->func = PLyInt_FromInt32;
+			break;
+		case INT8OID:
+			arg->func = PLyLong_FromInt64;
+			break;
+		case BYTEAOID:
+			arg->func = PLyBytes_FromBytea;
+			break;
+		default:
+			arg->func = PLyString_FromDatum;
+			break;
+	}
+
+	if (element_type)
+	{
+		char		dummy_delim;
+		Oid			funcid;
+
+		arg->elm = PLy_malloc0(sizeof(*arg->elm));
+		arg->elm->func = arg->func;
+		arg->func = PLyList_FromArray;
+		arg->elm->typoid = element_type;
+		arg->elm->typmod = -1;
+		get_type_io_data(element_type, IOFunc_output,
+						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
+						 &arg->elm->typioparam, &funcid);
+		perm_fmgr_info(funcid, &arg->elm->typfunc);
+	}
+}
+
+static PyObject *
+PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
+{
+	/*
+	 * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for
+	 * generating SQL from trigger functions, but those are only supported in
+	 * Python >= 2.3, and we support older versions.
+	 * http://docs.python.org/api/boolObjects.html
+	 */
+	if (DatumGetBool(d))
+		return PyBool_FromLong(1);
+	return PyBool_FromLong(0);
+}
+
+static PyObject *
+PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d)
+{
+	return PyFloat_FromDouble(DatumGetFloat4(d));
+}
+
+static PyObject *
+PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
+{
+	return PyFloat_FromDouble(DatumGetFloat8(d));
+}
+
+static PyObject *
+PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
+{
+	/*
+	 * Numeric is cast to a PyFloat: This results in a loss of precision Would
+	 * it be better to cast to PyString?
+	 */
+	Datum		f = DirectFunctionCall1(numeric_float8, d);
+	double		x = DatumGetFloat8(f);
+
+	return PyFloat_FromDouble(x);
+}
+
+static PyObject *
+PLyInt_FromInt16(PLyDatumToOb *arg, Datum d)
+{
+	return PyInt_FromLong(DatumGetInt16(d));
+}
+
+static PyObject *
+PLyInt_FromInt32(PLyDatumToOb *arg, Datum d)
+{
+	return PyInt_FromLong(DatumGetInt32(d));
+}
+
+static PyObject *
+PLyLong_FromInt64(PLyDatumToOb *arg, Datum d)
+{
+	/* on 32 bit platforms "long" may be too small */
+	if (sizeof(int64) > sizeof(long))
+		return PyLong_FromLongLong(DatumGetInt64(d));
+	else
+		return PyLong_FromLong(DatumGetInt64(d));
+}
+
+static PyObject *
+PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
+{
+	text	   *txt = DatumGetByteaP(d);
+	char	   *str = VARDATA(txt);
+	size_t		size = VARSIZE(txt) - VARHDRSZ;
+
+	return PyBytes_FromStringAndSize(str, size);
+}
+
+static PyObject *
+PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
+{
+	char	   *x = OutputFunctionCall(&arg->typfunc, d);
+	PyObject   *r = PyString_FromString(x);
+
+	pfree(x);
+	return r;
+}
+
+static PyObject *
+PLyList_FromArray(PLyDatumToOb *arg, Datum d)
+{
+	ArrayType  *array = DatumGetArrayTypeP(d);
+	PLyDatumToOb *elm = arg->elm;
+	PyObject   *list;
+	int			length;
+	int			lbound;
+	int			i;
+
+	if (ARR_NDIM(array) == 0)
+		return PyList_New(0);
+
+	if (ARR_NDIM(array) != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			  errmsg("cannot convert multidimensional array to Python list"),
+			  errdetail("PL/Python only supports one-dimensional arrays.")));
+
+	length = ARR_DIMS(array)[0];
+	lbound = ARR_LBOUND(array)[0];
+	list = PyList_New(length);
+
+	for (i = 0; i < length; i++)
+	{
+		Datum		elem;
+		bool		isnull;
+		int			offset;
+
+		offset = lbound + i;
+		elem = array_ref(array, 1, &offset, arg->typlen,
+						 elm->typlen, elm->typbyval, elm->typalign,
+						 &isnull);
+		if (isnull)
+		{
+			Py_INCREF(Py_None);
+			PyList_SET_ITEM(list, i, Py_None);
+		}
+		else
+			PyList_SET_ITEM(list, i, elm->func(elm, elem));
+	}
+
+	return list;
+}
+
+/*
+ * Convert a Python object to a PostgreSQL bool datum.	This can't go
+ * through the generic conversion function, because Python attaches a
+ * Boolean value to everything, more things than the PostgreSQL bool
+ * type can parse.
+ */
+static Datum
+PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	Datum		rv;
+
+	Assert(plrv != Py_None);
+	rv = BoolGetDatum(PyObject_IsTrue(plrv));
+
+	if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
+		domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
+
+	return rv;
+}
+
+/*
+ * Convert a Python object to a PostgreSQL bytea datum.  This doesn't
+ * go through the generic conversion function to circumvent problems
+ * with embedded nulls.  And it's faster this way.
+ */
+static Datum
+PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	PyObject   *volatile plrv_so = NULL;
+	Datum		rv;
+
+	Assert(plrv != Py_None);
+
+	plrv_so = PyObject_Bytes(plrv);
+	if (!plrv_so)
+		PLy_elog(ERROR, "could not create bytes representation of Python object");
+
+	PG_TRY();
+	{
+		char	   *plrv_sc = PyBytes_AsString(plrv_so);
+		size_t		len = PyBytes_Size(plrv_so);
+		size_t		size = len + VARHDRSZ;
+		bytea	   *result = palloc(size);
+
+		SET_VARSIZE(result, size);
+		memcpy(VARDATA(result), plrv_sc, len);
+		rv = PointerGetDatum(result);
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_so);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_so);
+
+	if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
+		domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
+
+	return rv;
+}
+
+
+/*
+ * Convert a Python object to a composite type. First look up the type's
+ * description, then route the Python object through the conversion function
+ * for obtaining PostgreSQL tuples.
+ */
+static Datum
+PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	HeapTuple	tuple = NULL;
+	Datum		rv;
+	PLyTypeInfo info;
+	TupleDesc	desc;
+
+	if (typmod != -1)
+		elog(ERROR, "received unnamed record type as input");
+
+	/* Create a dummy PLyTypeInfo */
+	MemSet(&info, 0, sizeof(PLyTypeInfo));
+	PLy_typeinfo_init(&info);
+	/* Mark it as needing output routines lookup */
+	info.is_rowtype = 2;
+
+	desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
+
+	/*
+	 * This will set up the dummy PLyTypeInfo's output conversion routines,
+	 * since we left is_rowtype as 2. A future optimisation could be caching
+	 * that info instead of looking it up every time a tuple is returned from
+	 * the function.
+	 */
+	tuple = PLyObject_ToTuple(&info, desc, plrv);
+
+	PLy_typeinfo_dealloc(&info);
+
+	if (tuple != NULL)
+		rv = HeapTupleGetDatum(tuple);
+	else
+		rv = (Datum) NULL;
+
+	return rv;
+}
+
+
+/*
+ * Generic conversion function: Convert PyObject to cstring and
+ * cstring into PostgreSQL type.
+ */
+static Datum
+PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	PyObject   *volatile plrv_bo = NULL;
+	Datum		rv;
+
+	Assert(plrv != Py_None);
+
+	if (PyUnicode_Check(plrv))
+		plrv_bo = PLyUnicode_Bytes(plrv);
+	else
+	{
+#if PY_MAJOR_VERSION >= 3
+		PyObject   *s = PyObject_Str(plrv);
+
+		plrv_bo = PLyUnicode_Bytes(s);
+		Py_XDECREF(s);
+#else
+		plrv_bo = PyObject_Str(plrv);
+#endif
+	}
+	if (!plrv_bo)
+		PLy_elog(ERROR, "could not create string representation of Python object");
+
+	PG_TRY();
+	{
+		char	   *plrv_sc = PyBytes_AsString(plrv_bo);
+		size_t		plen = PyBytes_Size(plrv_bo);
+		size_t		slen = strlen(plrv_sc);
+
+		if (slen < plen)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
+		else if (slen > plen)
+			elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
+		pg_verifymbstr(plrv_sc, slen, false);
+		rv = InputFunctionCall(&arg->typfunc,
+							   plrv_sc,
+							   arg->typioparam,
+							   typmod);
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_bo);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_bo);
+
+	return rv;
+}
+
+static Datum
+PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	ArrayType  *array;
+	int			i;
+	Datum	   *elems;
+	bool	   *nulls;
+	int			len;
+	int			lbs;
+
+	Assert(plrv != Py_None);
+
+	if (!PySequence_Check(plrv))
+		PLy_elog(ERROR, "return value of function with array return type is not a Python sequence");
+
+	len = PySequence_Length(plrv);
+	elems = palloc(sizeof(*elems) * len);
+	nulls = palloc(sizeof(*nulls) * len);
+
+	for (i = 0; i < len; i++)
+	{
+		PyObject   *obj = PySequence_GetItem(plrv, i);
+
+		if (obj == Py_None)
+			nulls[i] = true;
+		else
+		{
+			nulls[i] = false;
+
+			/*
+			 * We don't support arrays of row types yet, so the first argument
+			 * can be NULL.
+			 */
+			elems[i] = arg->elm->func(arg->elm, -1, obj);
+		}
+		Py_XDECREF(obj);
+	}
+
+	lbs = 1;
+	array = construct_md_array(elems, nulls, 1, &len, &lbs,
+							   get_element_type(arg->typoid), arg->elm->typlen, arg->elm->typbyval, arg->elm->typalign);
+	return PointerGetDatum(array);
+}
+
+static HeapTuple
+PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
+{
+	HeapTuple	tuple;
+	Datum	   *values;
+	bool	   *nulls;
+	volatile int i;
+
+	Assert(PyMapping_Check(mapping));
+
+	if (info->is_rowtype == 2)
+		PLy_output_tuple_funcs(info, desc);
+	Assert(info->is_rowtype == 1);
+
+	/* Build tuple */
+	values = palloc(sizeof(Datum) * desc->natts);
+	nulls = palloc(sizeof(bool) * desc->natts);
+	for (i = 0; i < desc->natts; ++i)
+	{
+		char	   *key;
+		PyObject   *volatile value;
+		PLyObToDatum *att;
+
+		if (desc->attrs[i]->attisdropped)
+		{
+			values[i] = (Datum) 0;
+			nulls[i] = true;
+			continue;
+		}
+
+		key = NameStr(desc->attrs[i]->attname);
+		value = NULL;
+		att = &info->out.r.atts[i];
+		PG_TRY();
+		{
+			value = PyMapping_GetItemString(mapping, key);
+			if (value == Py_None)
+			{
+				values[i] = (Datum) NULL;
+				nulls[i] = true;
+			}
+			else if (value)
+			{
+				values[i] = (att->func) (att, -1, value);
+				nulls[i] = false;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("key \"%s\" not found in mapping", key),
+						 errhint("To return null in a column, "
+								 "add the value None to the mapping with the key named after the column.")));
+
+			Py_XDECREF(value);
+			value = NULL;
+		}
+		PG_CATCH();
+		{
+			Py_XDECREF(value);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+
+	tuple = heap_form_tuple(desc, values, nulls);
+	ReleaseTupleDesc(desc);
+	pfree(values);
+	pfree(nulls);
+
+	return tuple;
+}
+
+
+static HeapTuple
+PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
+{
+	HeapTuple	tuple;
+	Datum	   *values;
+	bool	   *nulls;
+	volatile int idx;
+	volatile int i;
+
+	Assert(PySequence_Check(sequence));
+
+	/*
+	 * Check that sequence length is exactly same as PG tuple's. We actually
+	 * can ignore exceeding items or assume missing ones as null but to avoid
+	 * plpython developer's errors we are strict here
+	 */
+	idx = 0;
+	for (i = 0; i < desc->natts; i++)
+	{
+		if (!desc->attrs[i]->attisdropped)
+			idx++;
+	}
+	if (PySequence_Length(sequence) != idx)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("length of returned sequence did not match number of columns in row")));
+
+	if (info->is_rowtype == 2)
+		PLy_output_tuple_funcs(info, desc);
+	Assert(info->is_rowtype == 1);
+
+	/* Build tuple */
+	values = palloc(sizeof(Datum) * desc->natts);
+	nulls = palloc(sizeof(bool) * desc->natts);
+	idx = 0;
+	for (i = 0; i < desc->natts; ++i)
+	{
+		PyObject   *volatile value;
+		PLyObToDatum *att;
+
+		if (desc->attrs[i]->attisdropped)
+		{
+			values[i] = (Datum) 0;
+			nulls[i] = true;
+			continue;
+		}
+
+		value = NULL;
+		att = &info->out.r.atts[i];
+		PG_TRY();
+		{
+			value = PySequence_GetItem(sequence, idx);
+			Assert(value);
+			if (value == Py_None)
+			{
+				values[i] = (Datum) NULL;
+				nulls[i] = true;
+			}
+			else if (value)
+			{
+				values[i] = (att->func) (att, -1, value);
+				nulls[i] = false;
+			}
+
+			Py_XDECREF(value);
+			value = NULL;
+		}
+		PG_CATCH();
+		{
+			Py_XDECREF(value);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		idx++;
+	}
+
+	tuple = heap_form_tuple(desc, values, nulls);
+	ReleaseTupleDesc(desc);
+	pfree(values);
+	pfree(nulls);
+
+	return tuple;
+}
+
+
+static HeapTuple
+PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
+{
+	HeapTuple	tuple;
+	Datum	   *values;
+	bool	   *nulls;
+	volatile int i;
+
+	if (info->is_rowtype == 2)
+		PLy_output_tuple_funcs(info, desc);
+	Assert(info->is_rowtype == 1);
+
+	/* Build tuple */
+	values = palloc(sizeof(Datum) * desc->natts);
+	nulls = palloc(sizeof(bool) * desc->natts);
+	for (i = 0; i < desc->natts; ++i)
+	{
+		char	   *key;
+		PyObject   *volatile value;
+		PLyObToDatum *att;
+
+		if (desc->attrs[i]->attisdropped)
+		{
+			values[i] = (Datum) 0;
+			nulls[i] = true;
+			continue;
+		}
+
+		key = NameStr(desc->attrs[i]->attname);
+		value = NULL;
+		att = &info->out.r.atts[i];
+		PG_TRY();
+		{
+			value = PyObject_GetAttrString(object, key);
+			if (value == Py_None)
+			{
+				values[i] = (Datum) NULL;
+				nulls[i] = true;
+			}
+			else if (value)
+			{
+				values[i] = (att->func) (att, -1, value);
+				nulls[i] = false;
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("attribute \"%s\" does not exist in Python object", key),
+						 errhint("To return null in a column, "
+						   "let the returned object have an attribute named "
+								 "after column with value None.")));
+
+			Py_XDECREF(value);
+			value = NULL;
+		}
+		PG_CATCH();
+		{
+			Py_XDECREF(value);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+	}
+
+	tuple = heap_form_tuple(desc, values, nulls);
+	ReleaseTupleDesc(desc);
+	pfree(values);
+	pfree(nulls);
+
+	return tuple;
+}
+
+/*
+ * 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);
+}
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
new file mode 100644
index 00000000000..6708bf9c9b5
--- /dev/null
+++ b/src/pl/plpython/plpy_typeio.h
@@ -0,0 +1,107 @@
+/*
+ * src/pl/plpython/plpy_typeio.h
+ */
+
+#ifndef PLPY_TYPEIO_H
+#define PLPY_TYPEIO_H
+
+#include "access/htup.h"
+#include "fmgr.h"
+#include "storage/itemptr.h"
+
+struct PLyDatumToOb;
+typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *, Datum);
+
+typedef struct PLyDatumToOb
+{
+	PLyDatumToObFunc func;
+	FmgrInfo	typfunc;		/* The type's output function */
+	Oid			typoid;			/* The OID of the type */
+	int32		typmod;			/* The typmod of the type */
+	Oid			typioparam;
+	bool		typbyval;
+	int16		typlen;
+	char		typalign;
+	struct PLyDatumToOb *elm;
+} 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
+ */
+struct PLyObToDatum;
+typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *, int32, PyObject *);
+
+typedef struct PLyObToDatum
+{
+	PLyObToDatumFunc func;
+	FmgrInfo	typfunc;		/* The type's input function */
+	Oid			typoid;			/* The OID of the type */
+	int32		typmod;			/* The typmod of the type */
+	Oid			typioparam;
+	bool		typbyval;
+	int16		typlen;
+	char		typalign;
+	struct PLyObToDatum *elm;
+} 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 vice versa
+ */
+typedef struct PLyTypeInfo
+{
+	PLyTypeInput in;
+	PLyTypeOutput out;
+
+	/*
+	 * 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
+	 */
+	int			is_rowtype;
+	/* used to check if the type has been modified */
+	Oid			typ_relid;
+	TransactionId typrel_xmin;
+	ItemPointerData typrel_tid;
+} PLyTypeInfo;
+
+extern void PLy_typeinfo_init(PLyTypeInfo *);
+extern void PLy_typeinfo_dealloc(PLyTypeInfo *);
+
+extern void PLy_input_datum_func(PLyTypeInfo *, Oid, HeapTuple);
+extern void PLy_output_datum_func(PLyTypeInfo *, HeapTuple);
+
+extern void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
+extern void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
+
+extern void PLy_output_record_funcs(PLyTypeInfo *, TupleDesc);
+
+/* conversion from Python objects to heap tuples */
+extern HeapTuple PLyObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
+
+/* conversion from heap tuples to Python dictionaries */
+extern PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
+
+#endif	/* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c
new file mode 100644
index 00000000000..414b9d5445a
--- /dev/null
+++ b/src/pl/plpython/plpy_util.c
@@ -0,0 +1,125 @@
+/*
+ * utility functions
+ *
+ * src/pl/plpython/plpy_util.c
+ */
+
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "utils/memutils.h"
+#include "utils/palloc.h"
+
+#include "plpython.h"
+
+#include "plpy_util.h"
+
+#include "plpy_elog.h"
+
+
+void *
+PLy_malloc(size_t bytes)
+{
+	/* We need our allocations to be long-lived, so use TopMemoryContext */
+	return MemoryContextAlloc(TopMemoryContext, bytes);
+}
+
+void *
+PLy_malloc0(size_t bytes)
+{
+	void	   *ptr = PLy_malloc(bytes);
+
+	MemSet(ptr, 0, bytes);
+	return ptr;
+}
+
+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 */
+void
+PLy_free(void *ptr)
+{
+	pfree(ptr);
+}
+
+/*
+ * Convert a Python unicode object to a Python string/bytes object in
+ * PostgreSQL server encoding.	Reference ownership is passed to the
+ * caller.
+ */
+PyObject *
+PLyUnicode_Bytes(PyObject *unicode)
+{
+	PyObject   *rv;
+	const char *serverenc;
+
+	/*
+	 * Python understands almost all PostgreSQL encoding names, but it doesn't
+	 * know SQL_ASCII.
+	 */
+	if (GetDatabaseEncoding() == PG_SQL_ASCII)
+		serverenc = "ascii";
+	else
+		serverenc = GetDatabaseEncodingName();
+	rv = PyUnicode_AsEncodedString(unicode, serverenc, "strict");
+	if (rv == NULL)
+		PLy_elog(ERROR, "could not convert Python Unicode object to PostgreSQL server encoding");
+	return rv;
+}
+
+/*
+ * Convert a Python unicode object to a C string in PostgreSQL server
+ * encoding.  No Python object reference is passed out of this
+ * function.  The result is palloc'ed.
+ *
+ * Note that this function is disguised as PyString_AsString() when
+ * using Python 3.	That function retuns a pointer into the internal
+ * memory of the argument, which isn't exactly the interface of this
+ * function.  But in either case you get a rather short-lived
+ * reference that you ought to better leave alone.
+ */
+char *
+PLyUnicode_AsString(PyObject *unicode)
+{
+	PyObject   *o = PLyUnicode_Bytes(unicode);
+	char	   *rv = pstrdup(PyBytes_AsString(o));
+
+	Py_XDECREF(o);
+	return rv;
+}
+
+#if PY_MAJOR_VERSION >= 3
+/*
+ * Convert a C string in the PostgreSQL server encoding to a Python
+ * unicode object.	Reference ownership is passed to the caller.
+ */
+PyObject *
+PLyUnicode_FromString(const char *s)
+{
+	char	   *utf8string;
+	PyObject   *o;
+
+	utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s,
+													strlen(s),
+													GetDatabaseEncoding(),
+													PG_UTF8);
+
+	o = PyUnicode_FromString(utf8string);
+
+	if (utf8string != s)
+		pfree(utf8string);
+
+	return o;
+}
+#endif   /* PY_MAJOR_VERSION >= 3 */
diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h
new file mode 100644
index 00000000000..237d6c5751f
--- /dev/null
+++ b/src/pl/plpython/plpy_util.h
@@ -0,0 +1,21 @@
+/*--------------------------
+ * common utility functions
+ *--------------------------
+ */
+
+#ifndef PLPY_UTIL_H
+#define PLPY_UTIL_H
+
+extern void *PLy_malloc(size_t);
+extern void *PLy_malloc0(size_t);
+extern char *PLy_strdup(const char *);
+extern void PLy_free(void *);
+
+extern PyObject *PLyUnicode_Bytes(PyObject *unicode);
+extern char *PLyUnicode_AsString(PyObject *unicode);
+
+#if PY_MAJOR_VERSION >= 3
+extern PyObject *PLyUnicode_FromString(const char *s);
+#endif
+
+#endif	/* PLPY_UTIL_H */
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
deleted file mode 100644
index dce8ff247b6..00000000000
--- a/src/pl/plpython/plpython.c
+++ /dev/null
@@ -1,5439 +0,0 @@
-/**********************************************************************
- * plpython.c - python as a procedural language for PostgreSQL
- *
- *	src/pl/plpython/plpython.c
- *
- *********************************************************************
- */
-
-#include "postgres.h"
-
-/* system stuff */
-#include <unistd.h>
-#include <fcntl.h>
-
-/* postgreSQL stuff */
-#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 "mb/pg_wchar.h"
-#include "miscadmin.h"
-#include "nodes/makefuncs.h"
-#include "parser/parse_type.h"
-#include "tcop/tcopprot.h"
-#include "access/transam.h"
-#include "access/xact.h"
-#include "utils/builtins.h"
-#include "utils/hsearch.h"
-#include "utils/lsyscache.h"
-#include "utils/memutils.h"
-#include "utils/rel.h"
-#include "utils/syscache.h"
-#include "utils/typcache.h"
-
-/*
- * Undefine some things that get (re)defined in the
- * Python headers. They aren't used below and we've
- * already included all the headers we need, so this
- * should be pretty safe.
- */
-
-#undef _POSIX_C_SOURCE
-#undef _XOPEN_SOURCE
-#undef HAVE_STRERROR
-#undef HAVE_TZNAME
-
-/*
- * Sometimes python carefully scribbles on our *printf macros.
- * So we undefine them here and redefine them after it's done its dirty deed.
- */
-
-#ifdef USE_REPL_SNPRINTF
-#undef snprintf
-#undef vsnprintf
-#endif
-
-#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 <Python.h>
-#undef errcode
-#define _DEBUG
-#elif defined (_MSC_VER)
-#define errcode __msvc_errcode
-#include <Python.h>
-#undef errcode
-#else
-#include <Python.h>
-#endif
-
-/*
- * Py_ssize_t compat for Python <= 2.4
- */
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-/*
- * PyBool_FromLong is supported from 2.3.
- */
-#if PY_VERSION_HEX < 0x02030000
-#define PyBool_FromLong(x) PyInt_FromLong(x)
-#endif
-
-/*
- * Python 2/3 strings/unicode/bytes handling.  Python 2 has strings
- * and unicode, Python 3 has strings, which are unicode on the C
- * level, and bytes.  The porting convention, which is similarly used
- * in Python 2.6, is that "Unicode" is always unicode, and "Bytes" are
- * bytes in Python 3 and strings in Python 2.  Since we keep
- * supporting Python 2 and its usual strings, we provide a
- * compatibility layer for Python 3 that when asked to convert a C
- * string to a Python string it converts the C string from the
- * PostgreSQL server encoding to a Python Unicode object.
- */
-
-#if PY_VERSION_HEX < 0x02060000
-/* This is exactly the compatibility layer that Python 2.6 uses. */
-#define PyBytes_AsString PyString_AsString
-#define PyBytes_FromStringAndSize PyString_FromStringAndSize
-#define PyBytes_Size PyString_Size
-#define PyObject_Bytes PyObject_Str
-#endif
-
-#if PY_MAJOR_VERSION >= 3
-#define PyString_Check(x) 0
-#define PyString_AsString(x) PLyUnicode_AsString(x)
-#define PyString_FromString(x) PLyUnicode_FromString(x)
-#endif
-
-/*
- * Python 3 only has long.
- */
-#if PY_MAJOR_VERSION >= 3
-#define PyInt_FromLong(x) PyLong_FromLong(x)
-#define PyInt_AsLong(x) PyLong_AsLong(x)
-#endif
-
-/*
- * PyVarObject_HEAD_INIT was added in Python 2.6.  Its use is
- * necessary to handle both Python 2 and 3.  This replacement
- * definition is for Python <=2.5
- */
-#ifndef PyVarObject_HEAD_INIT
-#define PyVarObject_HEAD_INIT(type, size)		\
-		PyObject_HEAD_INIT(type) size,
-#endif
-
-/* Python 3 removed the Py_TPFLAGS_HAVE_ITER flag */
-#if PY_MAJOR_VERSION >= 3
-#define Py_TPFLAGS_HAVE_ITER 0
-#endif
-
-/* define our text domain for translations */
-#undef TEXTDOMAIN
-#define TEXTDOMAIN PG_TEXTDOMAIN("plpython")
-
-#include <compile.h>
-#include <eval.h>
-
-/* put back our snprintf and vsnprintf */
-#ifdef USE_REPL_SNPRINTF
-#ifdef snprintf
-#undef snprintf
-#endif
-#ifdef vsnprintf
-#undef vsnprintf
-#endif
-#ifdef __GNUC__
-#define vsnprintf(...)  pg_vsnprintf(__VA_ARGS__)
-#define snprintf(...)   pg_snprintf(__VA_ARGS__)
-#else
-#define vsnprintf               pg_vsnprintf
-#define snprintf                pg_snprintf
-#endif   /* __GNUC__ */
-#endif   /* USE_REPL_SNPRINTF */
-
-PG_MODULE_MAGIC;
-
-/* convert Postgresql Datum or tuple into a PyObject.
- * input to Python.  Tuples are converted to dictionary
- * objects.
- */
-
-struct PLyDatumToOb;
-typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *, Datum);
-
-typedef struct PLyDatumToOb
-{
-	PLyDatumToObFunc func;
-	FmgrInfo	typfunc;		/* The type's output function */
-	Oid			typoid;			/* The OID of the type */
-	int32		typmod;			/* The typmod of the type */
-	Oid			typioparam;
-	bool		typbyval;
-	int16		typlen;
-	char		typalign;
-	struct PLyDatumToOb *elm;
-} 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
- */
-
-struct PLyObToDatum;
-typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *, int32 typmod,
-											   PyObject *);
-
-typedef struct PLyObToDatum
-{
-	PLyObToDatumFunc func;
-	FmgrInfo	typfunc;		/* The type's input function */
-	Oid			typoid;			/* The OID of the type */
-	int32		typmod;			/* The typmod of the type */
-	Oid			typioparam;
-	bool		typbyval;
-	int16		typlen;
-	char		typalign;
-	struct PLyObToDatum *elm;
-} 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 vice versa
- */
-typedef struct PLyTypeInfo
-{
-	PLyTypeInput in;
-	PLyTypeOutput out;
-
-	/*
-	 * 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
-	 */
-	int			is_rowtype;
-	/* used to check if the type has been modified */
-	Oid			typ_relid;
-	TransactionId typrel_xmin;
-	ItemPointerData typrel_tid;
-} PLyTypeInfo;
-
-
-/* cached procedure data */
-typedef struct PLyProcedure
-{
-	char	   *proname;		/* SQL name of procedure */
-	char	   *pyname;			/* Python name of procedure */
-	TransactionId fn_xmin;
-	ItemPointerData fn_tid;
-	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	   *src;			/* textual procedure code, after mangling */
-	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 */
-} PLyProcedure;
-
-
-/* the procedure cache entry */
-typedef struct PLyProcedureEntry
-{
-	Oid			fn_oid;			/* hash key */
-	PLyProcedure *proc;
-} PLyProcedureEntry;
-
-/* explicit subtransaction data */
-typedef struct PLySubtransactionData
-{
-	MemoryContext oldcontext;
-	ResourceOwner oldowner;
-} PLySubtransactionData;
-
-
-/* Python objects */
-typedef struct PLyPlanObject
-{
-	PyObject_HEAD
-	SPIPlanPtr	plan;
-	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;
-
-typedef struct PLySubtransactionObject
-{
-	PyObject_HEAD
-	bool		started;
-	bool		exited;
-} PLySubtransactionObject;
-
-typedef struct PLyCursorObject
-{
-	PyObject_HEAD
-	char		*portalname;
-	PLyTypeInfo result;
-	bool		closed;
-} PLyCursorObject;
-
-/* A list of all known exceptions, generated from backend/utils/errcodes.txt */
-typedef struct ExceptionMap
-{
-	char	   *name;
-	char	   *classname;
-	int			sqlstate;
-} ExceptionMap;
-
-static const ExceptionMap exception_map[] = {
-#include "spiexceptions.h"
-	{NULL, NULL, 0}
-};
-
-/* A hash table mapping sqlstates to exceptions, for speedy lookup */
-static HTAB *PLy_spi_exceptions;
-
-typedef struct PLyExceptionEntry
-{
-	int			sqlstate;		/* hash key, must be first */
-	PyObject   *exc;			/* corresponding exception */
-} PLyExceptionEntry;
-
-
-/* function declarations */
-
-#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
-
-/* exported functions */
-Datum		plpython_validator(PG_FUNCTION_ARGS);
-Datum		plpython_call_handler(PG_FUNCTION_ARGS);
-Datum		plpython_inline_handler(PG_FUNCTION_ARGS);
-void		_PG_init(void);
-
-PG_FUNCTION_INFO_V1(plpython_validator);
-PG_FUNCTION_INFO_V1(plpython_call_handler);
-PG_FUNCTION_INFO_V1(plpython_inline_handler);
-
-/* most of the remaining of the declarations, all static */
-
-/*
- * These should only be called once from _PG_init.	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 and translation support */
-static void
-PLy_exception_set(PyObject *, const char *,...)
-__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
-
-/* same, with pluralized message */
-static 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)));
-
-/* like PLy_exception_set, but conserve more fields from ErrorData */
-static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
-
-/* 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 *,...)
-__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
-static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position);
-static void PLy_traceback(char **, char **, int *);
-
-static void *PLy_malloc(size_t);
-static void *PLy_malloc0(size_t);
-static char *PLy_strdup(const char *);
-static void PLy_free(void *);
-
-static PyObject *PLyUnicode_Bytes(PyObject *unicode);
-static char *PLyUnicode_AsString(PyObject *unicode);
-
-#if PY_MAJOR_VERSION >= 3
-static PyObject *PLyUnicode_FromString(const char *s);
-#endif
-
-/* 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(Oid fn_oid, bool is_trigger);
-
-static PLyProcedure *PLy_procedure_create(HeapTuple procTup,
-					 Oid fn_oid, bool is_trigger);
-
-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);
-static void PLy_output_record_funcs(PLyTypeInfo *, TupleDesc);
-
-/* conversion functions */
-static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyInt_FromInt16(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
-static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
-
-static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
-
-static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *);
-static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *);
-static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *);
-static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *);
-static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *);
-
-static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
-static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
-static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
-static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *);
-
-/*
- * Currently active plpython function
- */
-static PLyProcedure *PLy_curr_procedure = NULL;
-
-/* list of explicit subtransaction data */
-static List *explicit_subtransactions = NIL;
-
-static PyObject *PLy_interp_globals = NULL;
-static PyObject *PLy_interp_safe_globals = NULL;
-static HTAB *PLy_procedure_cache = NULL;
-static HTAB *PLy_trigger_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"
-};
-
-static char PLy_subtransaction_doc[] = {
-	"PostgreSQL subtransaction context manager"
-};
-
-static char PLy_cursor_doc[] = {
-	"Wrapper around a PostgreSQL cursor"
-};
-
-
-/*
- * 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);
-}
-
-static void
-plpython_error_callback(void *arg)
-{
-	if (PLy_curr_procedure)
-		errcontext("PL/Python function \"%s\"",
-				   PLy_procedure_name(PLy_curr_procedure));
-}
-
-static void
-plpython_inline_error_callback(void *arg)
-{
-	errcontext("PL/Python anonymous code block");
-}
-
-static void
-plpython_trigger_error_callback(void *arg)
-{
-	if (PLy_curr_procedure)
-		errcontext("while modifying trigger row");
-}
-
-static void
-plpython_return_error_callback(void *arg)
-{
-	if (PLy_curr_procedure)
-		errcontext("while creating return value");
-}
-
-static bool
-PLy_procedure_is_trigger(Form_pg_proc procStruct)
-{
-	return (procStruct->prorettype == TRIGGEROID ||
-			(procStruct->prorettype == OPAQUEOID &&
-			 procStruct->pronargs == 0));
-}
-
-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();
-}
-
-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_trigger_handler(fcinfo, proc);
-			retval = PointerGetDatum(trv);
-		}
-		else
-		{
-			proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
-			PLy_curr_procedure = proc;
-			retval = PLy_function_handler(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;
-}
-
-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_function_handler(&fake_fcinfo, &proc);
-	}
-	PG_CATCH();
-	{
-		PLy_procedure_delete(&proc);
-		PLy_curr_procedure = save_curr_proc;
-		PyErr_Clear();
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	PLy_procedure_delete(&proc);
-
-	/* Pop the error context stack */
-	error_context_stack = plerrcontext.previous;
-
-	PLy_curr_procedure = save_curr_proc;
-
-	PG_RETURN_VOID();
-}
-
-/* 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;
-	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;
-}
-
-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 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;
-}
-
-
-
-/* function handler and friends */
-static Datum
-PLy_function_handler(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;
-}
-
-/*
- * 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);
-	}
-}
-
-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;
-}
-
-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]);
-}
-
-/*
- * Check if our cached information about a datatype is still valid
- */
-static bool
-PLy_procedure_argument_valid(PLyTypeInfo *arg)
-{
-	HeapTuple	relTup;
-	bool		valid;
-
-	/* Nothing to cache unless type is composite */
-	if (arg->is_rowtype != 1)
-		return true;
-
-	/*
-	 * Zero typ_relid means that we got called on an output argument of a
-	 * function returning a unnamed record type; the info for it can't change.
-	 */
-	if (!OidIsValid(arg->typ_relid))
-		return true;
-
-	/* Else we should have some cached data */
-	Assert(TransactionIdIsValid(arg->typrel_xmin));
-	Assert(ItemPointerIsValid(&arg->typrel_tid));
-
-	/* Get the pg_class tuple for the data type */
-	relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
-	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
-
-	/* If it has changed, the cached data is not valid */
-	valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
-			 ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
-
-	ReleaseSysCache(relTup);
-
-	return valid;
-}
-
-/*
- * Decide whether a cached PLyProcedure struct is still valid
- */
-static bool
-PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
-{
-	int			i;
-	bool		valid;
-
-	Assert(proc != NULL);
-
-	/* If the pg_proc tuple has changed, it's not valid */
-	if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
-		  ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
-		return false;
-
-	/* Else check the input argument datatypes */
-	valid = true;
-	for (i = 0; i < proc->nargs; i++)
-	{
-		valid = PLy_procedure_argument_valid(&proc->args[i]);
-
-		/* Short-circuit on first changed argument */
-		if (!valid)
-			break;
-	}
-
-	/* if the output type is composite, it might have changed */
-	if (valid)
-		valid = PLy_procedure_argument_valid(&proc->result);
-
-	return valid;
-}
-
-
-/*
- * 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(Oid fn_oid, bool is_trigger)
-{
-	HeapTuple	procTup;
-	PLyProcedureEntry *volatile entry;
-	bool		found;
-
-	procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
-	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for function %u", fn_oid);
-
-	/* Look for the function in the corresponding cache */
-	if (is_trigger)
-		entry = hash_search(PLy_trigger_cache,
-							&fn_oid, HASH_ENTER, &found);
-	else
-		entry = hash_search(PLy_procedure_cache,
-							&fn_oid, HASH_ENTER, &found);
-
-	PG_TRY();
-	{
-		if (!found)
-		{
-			/* Haven't found it, create a new cache entry */
-			entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
-		}
-		else if (!PLy_procedure_valid(entry->proc, procTup))
-		{
-			/* Found it, but it's invalid, free and reuse the cache entry */
-			PLy_procedure_delete(entry->proc);
-			PLy_free(entry->proc);
-			entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
-		}
-		/* Found it and it's valid, it's fine to use it */
-	}
-	PG_CATCH();
-	{
-		/* Do not leave an uninitialised entry in the cache */
-		if (is_trigger)
-			hash_search(PLy_trigger_cache,
-						&fn_oid, HASH_REMOVE, NULL);
-		else
-			hash_search(PLy_procedure_cache,
-						&fn_oid, HASH_REMOVE, NULL);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	ReleaseSysCache(procTup);
-
-	return entry->proc;
-}
-
-/*
- * Create a new PLyProcedure structure
- */
-static PLyProcedure *
-PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
-{
-	char		procName[NAMEDATALEN + 256];
-	Form_pg_proc procStruct;
-	PLyProcedure *volatile proc;
-	char	   *volatile procSource = NULL;
-	Datum		prosrcdatum;
-	bool		isnull;
-	int			i,
-				rv;
-
-	procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-	rv = snprintf(procName, sizeof(procName),
-				  "__plpython_procedure_%s_%u",
-				  NameStr(procStruct->proname),
-				  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_tid = procTup->t_self;
-	/* 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 = NULL;
-	proc->is_setof = procStruct->proretset;
-	proc->setof = NULL;
-	proc->src = 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 (!is_trigger)
-		{
-			HeapTuple	rvTypeTup;
-			Form_pg_type rvTypeStruct;
-
-			rvTypeTup = SearchSysCache1(TYPEOID,
-								   ObjectIdGetDatum(procStruct->prorettype));
-			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 or record */
-			if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
-			{
-				if (procStruct->prorettype == TRIGGEROID)
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("trigger functions can only be called as triggers")));
-				else if (procStruct->prorettype != VOIDOID &&
-						 procStruct->prorettype != RECORDOID)
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						  errmsg("PL/Python functions cannot return type %s",
-								 format_type_be(procStruct->prorettype))));
-			}
-
-			if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
-				procStruct->prorettype == RECORDOID)
-			{
-				/*
-				 * Tuple: set up later, during first call to
-				 * PLy_function_handler
-				 */
-				proc->result.out.d.typoid = procStruct->prorettype;
-				proc->result.out.d.typmod = -1;
-				proc->result.is_rowtype = 2;
-			}
-			else
-			{
-				/* do the real work */
-				PLy_output_datum_func(&proc->result, rvTypeTup);
-			}
-
-			ReleaseSysCache(rvTypeTup);
-		}
-
-		/*
-		 * Now get information required for input conversion of the
-		 * procedure's arguments.  Note that we ignore output arguments here.
-		 * If the function returns record, those I/O functions will be set up
-		 * when the function is first called.
-		 */
-		if (procStruct->pronargs)
-		{
-			Oid		   *types;
-			char	  **names,
-					   *modes;
-			int			i,
-						pos,
-						total;
-
-			/* extract argument type info from the pg_proc tuple */
-			total = get_func_arg_info(procTup, &types, &names, &modes);
-
-			/* count number of in+inout args into proc->nargs */
-			if (modes == NULL)
-				proc->nargs = total;
-			else
-			{
-				/* proc->nargs was initialized to 0 above */
-				for (i = 0; i < total; i++)
-				{
-					if (modes[i] != PROARGMODE_OUT &&
-						modes[i] != PROARGMODE_TABLE)
-						(proc->nargs)++;
-				}
-			}
-
-			proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs);
-			for (i = pos = 0; i < total; i++)
-			{
-				HeapTuple	argTypeTup;
-				Form_pg_type argTypeStruct;
-
-				if (modes &&
-					(modes[i] == PROARGMODE_OUT ||
-					 modes[i] == PROARGMODE_TABLE))
-					continue;	/* skip OUT arguments */
-
-				Assert(types[i] == procStruct->proargtypes.values[pos]);
-
-				argTypeTup = SearchSysCache1(TYPEOID,
-											 ObjectIdGetDatum(types[i]));
-				if (!HeapTupleIsValid(argTypeTup))
-					elog(ERROR, "cache lookup failed for type %u", types[i]);
-				argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
-
-				/* check argument type is OK, set up I/O function info */
-				switch (argTypeStruct->typtype)
-				{
-					case TYPTYPE_PSEUDO:
-						/* Disallow pseudotype argument */
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						  errmsg("PL/Python functions cannot accept type %s",
-								 format_type_be(types[i]))));
-						break;
-					case TYPTYPE_COMPOSITE:
-						/* we'll set IO funcs at first call */
-						proc->args[pos].is_rowtype = 2;
-						break;
-					default:
-						PLy_input_datum_func(&(proc->args[pos]),
-											 types[i],
-											 argTypeTup);
-						break;
-				}
-
-				/* get argument name */
-				proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL;
-
-				ReleaseSysCache(argTypeTup);
-
-				pos++;
-			}
-		}
-
-		/*
-		 * get the text of the function.
-		 */
-		prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
-									  Anum_pg_proc_prosrc, &isnull);
-		if (isnull)
-			elog(ERROR, "null prosrc");
-		procSource = TextDatumGetCString(prosrcdatum);
-
-		PLy_procedure_compile(proc, procSource);
-
-		pfree(procSource);
-		procSource = NULL;
-	}
-	PG_CATCH();
-	{
-		PLy_procedure_delete(proc);
-		if (procSource)
-			pfree(procSource);
-
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	return proc;
-}
-
-/*
- * Insert the procedure into the Python interpreter
- */
-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);
-	/* Save the mangled source for later inclusion in tracebacks */
-	proc->src = PLy_strdup(msrc);
-	crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
-	pfree(msrc);
-
-	if (crv != NULL)
-	{
-		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, "<string>", Py_eval_input);
-		if (proc->code != NULL)
-			return;
-	}
-
-	if (proc->proname)
-		PLy_elog(ERROR, "could not compile PL/Python function \"%s\"",
-				 proc->proname);
-	else
-		PLy_elog(ERROR, "could not compile anonymous PL/Python code block");
-}
-
-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 = palloc(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);
-	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->src)
-		PLy_free(proc->src);
-	if (proc->argnames)
-		PLy_free(proc->argnames);
-}
-
-/*
- * Conversion functions.  Remember output from Python is input to
- * PostgreSQL, and vice 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;
-
-	if (arg->in.r.natts != desc->natts)
-	{
-		if (arg->in.r.atts)
-			PLy_free(arg->in.r.atts);
-		arg->in.r.natts = desc->natts;
-		arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
-	}
-
-	/* Can this be an unnamed tuple? If not, then an Assert would be enough */
-	if (desc->tdtypmod != -1)
-		elog(ERROR, "received unnamed record type as input");
-
-	Assert(OidIsValid(desc->tdtypeid));
-
-	/*
-	 * RECORDOID means we got called to create input functions for a tuple
-	 * fetched by plpy.execute or for an anonymous record type
-	 */
-	if (desc->tdtypeid != RECORDOID)
-	{
-		HeapTuple	relTup;
-
-		/* Get the pg_class tuple corresponding to the type of the input */
-		arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
-		relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
-		if (!HeapTupleIsValid(relTup))
-			elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
-
-		/* Remember XMIN and TID for later validation if cache is still OK */
-		arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
-		arg->typrel_tid = relTup->t_self;
-
-		ReleaseSysCache(relTup);
-	}
-
-	for (i = 0; i < desc->natts; i++)
-	{
-		HeapTuple	typeTup;
-
-		if (desc->attrs[i]->attisdropped)
-			continue;
-
-		if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid)
-			continue;			/* already set up this entry */
-
-		typeTup = SearchSysCache1(TYPEOID,
-								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
-		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_record_funcs(PLyTypeInfo *arg, TupleDesc desc)
-{
-	/*
-	 * If the output record functions are already set, we just have to check
-	 * if the record descriptor has not changed
-	 */
-	if ((arg->is_rowtype == 1) &&
-		(arg->out.d.typmod != -1) &&
-		(arg->out.d.typmod == desc->tdtypmod))
-		return;
-
-	/* bless the record to make it known to the typcache lookup code */
-	BlessTupleDesc(desc);
-	/* save the freshly generated typmod */
-	arg->out.d.typmod = desc->tdtypmod;
-	/* proceed with normal I/O function caching */
-	PLy_output_tuple_funcs(arg, desc);
-
-	/*
-	 * it should change is_rowtype to 1, so we won't go through this again
-	 * unless the the output record description changes
-	 */
-	Assert(arg->is_rowtype == 1);
-}
-
-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;
-
-	if (arg->out.r.natts != desc->natts)
-	{
-		if (arg->out.r.atts)
-			PLy_free(arg->out.r.atts);
-		arg->out.r.natts = desc->natts;
-		arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb));
-	}
-
-	Assert(OidIsValid(desc->tdtypeid));
-
-	/*
-	 * RECORDOID means we got called to create output functions for an
-	 * anonymous record type
-	 */
-	if (desc->tdtypeid != RECORDOID)
-	{
-		HeapTuple	relTup;
-
-		/* Get the pg_class tuple corresponding to the type of the output */
-		arg->typ_relid = typeidTypeRelid(desc->tdtypeid);
-		relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
-		if (!HeapTupleIsValid(relTup))
-			elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
-
-		/* Remember XMIN and TID for later validation if cache is still OK */
-		arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data);
-		arg->typrel_tid = relTup->t_self;
-
-		ReleaseSysCache(relTup);
-	}
-
-	for (i = 0; i < desc->natts; i++)
-	{
-		HeapTuple	typeTup;
-
-		if (desc->attrs[i]->attisdropped)
-			continue;
-
-		if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid)
-			continue;			/* already set up this entry */
-
-		typeTup = SearchSysCache1(TYPEOID,
-								  ObjectIdGetDatum(desc->attrs[i]->atttypid));
-		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);
-	Oid			element_type;
-
-	perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
-	arg->typoid = HeapTupleGetOid(typeTup);
-	arg->typmod = -1;
-	arg->typioparam = getTypeIOParam(typeTup);
-	arg->typbyval = typeStruct->typbyval;
-
-	element_type = get_element_type(arg->typoid);
-
-	/*
-	 * Select a conversion function to convert Python objects to PostgreSQL
-	 * datums.	Most data types can go through the generic function.
-	 */
-	switch (getBaseType(element_type ? element_type : arg->typoid))
-	{
-		case BOOLOID:
-			arg->func = PLyObject_ToBool;
-			break;
-		case BYTEAOID:
-			arg->func = PLyObject_ToBytea;
-			break;
-		default:
-			arg->func = PLyObject_ToDatum;
-			break;
-	}
-
-	/* Composite types need their own input routine, though */
-	if (typeStruct->typtype == TYPTYPE_COMPOSITE)
-	{
-		arg->func = PLyObject_ToComposite;
-	}
-
-	if (element_type)
-	{
-		char		dummy_delim;
-		Oid			funcid;
-
-		if (type_is_rowtype(element_type))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("PL/Python functions cannot return type %s",
-							format_type_be(arg->typoid)),
-					 errdetail("PL/Python does not support conversion to arrays of row types.")));
-
-		arg->elm = PLy_malloc0(sizeof(*arg->elm));
-		arg->elm->func = arg->func;
-		arg->func = PLySequence_ToArray;
-
-		arg->elm->typoid = element_type;
-		arg->elm->typmod = -1;
-		get_type_io_data(element_type, IOFunc_input,
-						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
-						 &arg->elm->typioparam, &funcid);
-		perm_fmgr_info(funcid, &arg->elm->typfunc);
-	}
-}
-
-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);
-	Oid			element_type = get_element_type(typeOid);
-
-	/* Get the type's conversion information */
-	perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
-	arg->typoid = HeapTupleGetOid(typeTup);
-	arg->typmod = -1;
-	arg->typioparam = getTypeIOParam(typeTup);
-	arg->typbyval = typeStruct->typbyval;
-	arg->typlen = typeStruct->typlen;
-	arg->typalign = typeStruct->typalign;
-
-	/* Determine which kind of Python object we will convert to */
-	switch (getBaseType(element_type ? element_type : typeOid))
-	{
-		case BOOLOID:
-			arg->func = PLyBool_FromBool;
-			break;
-		case FLOAT4OID:
-			arg->func = PLyFloat_FromFloat4;
-			break;
-		case FLOAT8OID:
-			arg->func = PLyFloat_FromFloat8;
-			break;
-		case NUMERICOID:
-			arg->func = PLyFloat_FromNumeric;
-			break;
-		case INT2OID:
-			arg->func = PLyInt_FromInt16;
-			break;
-		case INT4OID:
-			arg->func = PLyInt_FromInt32;
-			break;
-		case INT8OID:
-			arg->func = PLyLong_FromInt64;
-			break;
-		case BYTEAOID:
-			arg->func = PLyBytes_FromBytea;
-			break;
-		default:
-			arg->func = PLyString_FromDatum;
-			break;
-	}
-
-	if (element_type)
-	{
-		char		dummy_delim;
-		Oid			funcid;
-
-		arg->elm = PLy_malloc0(sizeof(*arg->elm));
-		arg->elm->func = arg->func;
-		arg->func = PLyList_FromArray;
-		arg->elm->typoid = element_type;
-		arg->elm->typmod = -1;
-		get_type_io_data(element_type, IOFunc_output,
-						 &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim,
-						 &arg->elm->typioparam, &funcid);
-		perm_fmgr_info(funcid, &arg->elm->typfunc);
-	}
-}
-
-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;
-	arg->typ_relid = InvalidOid;
-	arg->typrel_xmin = InvalidTransactionId;
-	ItemPointerSetInvalid(&arg->typrel_tid);
-}
-
-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);
-	}
-}
-
-static PyObject *
-PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
-{
-	/*
-	 * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for
-	 * generating SQL from trigger functions, but those are only supported in
-	 * Python >= 2.3, and we support older versions.
-	 * http://docs.python.org/api/boolObjects.html
-	 */
-	if (DatumGetBool(d))
-		return PyBool_FromLong(1);
-	return PyBool_FromLong(0);
-}
-
-static PyObject *
-PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d)
-{
-	return PyFloat_FromDouble(DatumGetFloat4(d));
-}
-
-static PyObject *
-PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
-{
-	return PyFloat_FromDouble(DatumGetFloat8(d));
-}
-
-static PyObject *
-PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
-{
-	/*
-	 * Numeric is cast to a PyFloat: This results in a loss of precision Would
-	 * it be better to cast to PyString?
-	 */
-	Datum		f = DirectFunctionCall1(numeric_float8, d);
-	double		x = DatumGetFloat8(f);
-
-	return PyFloat_FromDouble(x);
-}
-
-static PyObject *
-PLyInt_FromInt16(PLyDatumToOb *arg, Datum d)
-{
-	return PyInt_FromLong(DatumGetInt16(d));
-}
-
-static PyObject *
-PLyInt_FromInt32(PLyDatumToOb *arg, Datum d)
-{
-	return PyInt_FromLong(DatumGetInt32(d));
-}
-
-static PyObject *
-PLyLong_FromInt64(PLyDatumToOb *arg, Datum d)
-{
-	/* on 32 bit platforms "long" may be too small */
-	if (sizeof(int64) > sizeof(long))
-		return PyLong_FromLongLong(DatumGetInt64(d));
-	else
-		return PyLong_FromLong(DatumGetInt64(d));
-}
-
-static PyObject *
-PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
-{
-	text	   *txt = DatumGetByteaP(d);
-	char	   *str = VARDATA(txt);
-	size_t		size = VARSIZE(txt) - VARHDRSZ;
-
-	return PyBytes_FromStringAndSize(str, size);
-}
-
-static PyObject *
-PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
-{
-	char	   *x = OutputFunctionCall(&arg->typfunc, d);
-	PyObject   *r = PyString_FromString(x);
-
-	pfree(x);
-	return r;
-}
-
-static PyObject *
-PLyList_FromArray(PLyDatumToOb *arg, Datum d)
-{
-	ArrayType  *array = DatumGetArrayTypeP(d);
-	PLyDatumToOb *elm = arg->elm;
-	PyObject   *list;
-	int			length;
-	int			lbound;
-	int			i;
-
-	if (ARR_NDIM(array) == 0)
-		return PyList_New(0);
-
-	if (ARR_NDIM(array) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			  errmsg("cannot convert multidimensional array to Python list"),
-			  errdetail("PL/Python only supports one-dimensional arrays.")));
-
-	length = ARR_DIMS(array)[0];
-	lbound = ARR_LBOUND(array)[0];
-	list = PyList_New(length);
-
-	for (i = 0; i < length; i++)
-	{
-		Datum		elem;
-		bool		isnull;
-		int			offset;
-
-		offset = lbound + i;
-		elem = array_ref(array, 1, &offset, arg->typlen,
-						 elm->typlen, elm->typbyval, elm->typalign,
-						 &isnull);
-		if (isnull)
-		{
-			Py_INCREF(Py_None);
-			PyList_SET_ITEM(list, i, Py_None);
-		}
-		else
-			PyList_SET_ITEM(list, i, elm->func(elm, elem));
-	}
-
-	return list;
-}
-
-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 new dictionary");
-
-	PG_TRY();
-	{
-		for (i = 0; i < info->in.r.natts; i++)
-		{
-			char	   *key;
-			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
-			{
-				value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
-				PyDict_SetItemString(dict, key, value);
-				Py_DECREF(value);
-			}
-		}
-	}
-	PG_CATCH();
-	{
-		Py_DECREF(dict);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	return dict;
-}
-
-/*
- *	Convert a Python object to a PostgreSQL tuple, using all supported
- *	conversion methods: tuple as a sequence, as a mapping or as an object that
- *	has __getattr__ support.
- */
-static HeapTuple
-PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
-{
-	HeapTuple	tuple;
-
-	if (PySequence_Check(plrv))
-		/* composite type as sequence (tuple, list etc) */
-		tuple = PLySequence_ToTuple(info, desc, plrv);
-	else if (PyMapping_Check(plrv))
-		/* composite type as mapping (currently only dict) */
-		tuple = PLyMapping_ToTuple(info, desc, plrv);
-	else
-		/* returned as smth, must provide method __getattr__(name) */
-		tuple = PLyGenericObject_ToTuple(info, desc, plrv);
-
-	return tuple;
-}
-
-/*
- * Convert a Python object to a PostgreSQL bool datum.	This can't go
- * through the generic conversion function, because Python attaches a
- * Boolean value to everything, more things than the PostgreSQL bool
- * type can parse.
- */
-static Datum
-PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
-{
-	Datum		rv;
-
-	Assert(plrv != Py_None);
-	rv = BoolGetDatum(PyObject_IsTrue(plrv));
-
-	if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
-		domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
-
-	return rv;
-}
-
-/*
- * Convert a Python object to a PostgreSQL bytea datum.  This doesn't
- * go through the generic conversion function to circumvent problems
- * with embedded nulls.  And it's faster this way.
- */
-static Datum
-PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
-{
-	PyObject   *volatile plrv_so = NULL;
-	Datum		rv;
-
-	Assert(plrv != Py_None);
-
-	plrv_so = PyObject_Bytes(plrv);
-	if (!plrv_so)
-		PLy_elog(ERROR, "could not create bytes representation of Python object");
-
-	PG_TRY();
-	{
-		char	   *plrv_sc = PyBytes_AsString(plrv_so);
-		size_t		len = PyBytes_Size(plrv_so);
-		size_t		size = len + VARHDRSZ;
-		bytea	   *result = palloc(size);
-
-		SET_VARSIZE(result, size);
-		memcpy(VARDATA(result), plrv_sc, len);
-		rv = PointerGetDatum(result);
-	}
-	PG_CATCH();
-	{
-		Py_XDECREF(plrv_so);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	Py_XDECREF(plrv_so);
-
-	if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
-		domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
-
-	return rv;
-}
-
-
-/*
- * Convert a Python object to a composite type. First look up the type's
- * description, then route the Python object through the conversion function
- * for obtaining PostgreSQL tuples.
- */
-static Datum
-PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
-{
-	HeapTuple	tuple = NULL;
-	Datum		rv;
-	PLyTypeInfo info;
-	TupleDesc	desc;
-
-	if (typmod != -1)
-		elog(ERROR, "received unnamed record type as input");
-
-	/* Create a dummy PLyTypeInfo */
-	MemSet(&info, 0, sizeof(PLyTypeInfo));
-	PLy_typeinfo_init(&info);
-	/* Mark it as needing output routines lookup */
-	info.is_rowtype = 2;
-
-	desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
-
-	/*
-	 * This will set up the dummy PLyTypeInfo's output conversion routines,
-	 * since we left is_rowtype as 2. A future optimisation could be caching
-	 * that info instead of looking it up every time a tuple is returned from
-	 * the function.
-	 */
-	tuple = PLyObject_ToTuple(&info, desc, plrv);
-
-	PLy_typeinfo_dealloc(&info);
-
-	if (tuple != NULL)
-		rv = HeapTupleGetDatum(tuple);
-	else
-		rv = (Datum) NULL;
-
-	return rv;
-}
-
-
-/*
- * Generic conversion function: Convert PyObject to cstring and
- * cstring into PostgreSQL type.
- */
-static Datum
-PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
-{
-	PyObject   *volatile plrv_bo = NULL;
-	Datum		rv;
-
-	Assert(plrv != Py_None);
-
-	if (PyUnicode_Check(plrv))
-		plrv_bo = PLyUnicode_Bytes(plrv);
-	else
-	{
-#if PY_MAJOR_VERSION >= 3
-		PyObject   *s = PyObject_Str(plrv);
-
-		plrv_bo = PLyUnicode_Bytes(s);
-		Py_XDECREF(s);
-#else
-		plrv_bo = PyObject_Str(plrv);
-#endif
-	}
-	if (!plrv_bo)
-		PLy_elog(ERROR, "could not create string representation of Python object");
-
-	PG_TRY();
-	{
-		char	   *plrv_sc = PyBytes_AsString(plrv_bo);
-		size_t		plen = PyBytes_Size(plrv_bo);
-		size_t		slen = strlen(plrv_sc);
-
-		if (slen < plen)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
-		else if (slen > plen)
-			elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
-		pg_verifymbstr(plrv_sc, slen, false);
-		rv = InputFunctionCall(&arg->typfunc,
-							   plrv_sc,
-							   arg->typioparam,
-							   typmod);
-	}
-	PG_CATCH();
-	{
-		Py_XDECREF(plrv_bo);
-		PG_RE_THROW();
-	}
-	PG_END_TRY();
-
-	Py_XDECREF(plrv_bo);
-
-	return rv;
-}
-
-static Datum
-PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
-{
-	ArrayType  *array;
-	int			i;
-	Datum	   *elems;
-	bool	   *nulls;
-	int			len;
-	int			lbs;
-
-	Assert(plrv != Py_None);
-
-	if (!PySequence_Check(plrv))
-		PLy_elog(ERROR, "return value of function with array return type is not a Python sequence");
-
-	len = PySequence_Length(plrv);
-	elems = palloc(sizeof(*elems) * len);
-	nulls = palloc(sizeof(*nulls) * len);
-
-	for (i = 0; i < len; i++)
-	{
-		PyObject   *obj = PySequence_GetItem(plrv, i);
-
-		if (obj == Py_None)
-			nulls[i] = true;
-		else
-		{
-			nulls[i] = false;
-
-			/*
-			 * We don't support arrays of row types yet, so the first argument
-			 * can be NULL.
-			 */
-			elems[i] = arg->elm->func(arg->elm, -1, obj);
-		}
-		Py_XDECREF(obj);
-	}
-
-	lbs = 1;
-	array = construct_md_array(elems, nulls, 1, &len, &lbs,
-							   get_element_type(arg->typoid), arg->elm->typlen, arg->elm->typbyval, arg->elm->typalign);
-	return PointerGetDatum(array);
-}
-
-static HeapTuple
-PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping)
-{
-	HeapTuple	tuple;
-	Datum	   *values;
-	bool	   *nulls;
-	volatile int i;
-
-	Assert(PyMapping_Check(mapping));
-
-	if (info->is_rowtype == 2)
-		PLy_output_tuple_funcs(info, desc);
-	Assert(info->is_rowtype == 1);
-
-	/* Build tuple */
-	values = palloc(sizeof(Datum) * desc->natts);
-	nulls = palloc(sizeof(bool) * desc->natts);
-	for (i = 0; i < desc->natts; ++i)
-	{
-		char	   *key;
-		PyObject   *volatile value;
-		PLyObToDatum *att;
-
-		if (desc->attrs[i]->attisdropped)
-		{
-			values[i] = (Datum) 0;
-			nulls[i] = true;
-			continue;
-		}
-
-		key = NameStr(desc->attrs[i]->attname);
-		value = NULL;
-		att = &info->out.r.atts[i];
-		PG_TRY();
-		{
-			value = PyMapping_GetItemString(mapping, key);
-			if (value == Py_None)
-			{
-				values[i] = (Datum) NULL;
-				nulls[i] = true;
-			}
-			else if (value)
-			{
-				values[i] = (att->func) (att, -1, value);
-				nulls[i] = false;
-			}
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("key \"%s\" not found in mapping", key),
-						 errhint("To return null in a column, "
-								 "add the value None to the mapping with the key named after the column.")));
-
-			Py_XDECREF(value);
-			value = NULL;
-		}
-		PG_CATCH();
-		{
-			Py_XDECREF(value);
-			PG_RE_THROW();
-		}
-		PG_END_TRY();
-	}
-
-	tuple = heap_form_tuple(desc, values, nulls);
-	ReleaseTupleDesc(desc);
-	pfree(values);
-	pfree(nulls);
-
-	return tuple;
-}
-
-
-static HeapTuple
-PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence)
-{
-	HeapTuple	tuple;
-	Datum	   *values;
-	bool	   *nulls;
-	volatile int idx;
-	volatile int i;
-
-	Assert(PySequence_Check(sequence));
-
-	/*
-	 * Check that sequence length is exactly same as PG tuple's. We actually
-	 * can ignore exceeding items or assume missing ones as null but to avoid
-	 * plpython developer's errors we are strict here
-	 */
-	idx = 0;
-	for (i = 0; i < desc->natts; i++)
-	{
-		if (!desc->attrs[i]->attisdropped)
-			idx++;
-	}
-	if (PySequence_Length(sequence) != idx)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("length of returned sequence did not match number of columns in row")));
-
-	if (info->is_rowtype == 2)
-		PLy_output_tuple_funcs(info, desc);
-	Assert(info->is_rowtype == 1);
-
-	/* Build tuple */
-	values = palloc(sizeof(Datum) * desc->natts);
-	nulls = palloc(sizeof(bool) * desc->natts);
-	idx = 0;
-	for (i = 0; i < desc->natts; ++i)
-	{
-		PyObject   *volatile value;
-		PLyObToDatum *att;
-
-		if (desc->attrs[i]->attisdropped)
-		{
-			values[i] = (Datum) 0;
-			nulls[i] = true;
-			continue;
-		}
-
-		value = NULL;
-		att = &info->out.r.atts[i];
-		PG_TRY();
-		{
-			value = PySequence_GetItem(sequence, idx);
-			Assert(value);
-			if (value == Py_None)
-			{
-				values[i] = (Datum) NULL;
-				nulls[i] = true;
-			}
-			else if (value)
-			{
-				values[i] = (att->func) (att, -1, value);
-				nulls[i] = false;
-			}
-
-			Py_XDECREF(value);
-			value = NULL;
-		}
-		PG_CATCH();
-		{
-			Py_XDECREF(value);
-			PG_RE_THROW();
-		}
-		PG_END_TRY();
-
-		idx++;
-	}
-
-	tuple = heap_form_tuple(desc, values, nulls);
-	ReleaseTupleDesc(desc);
-	pfree(values);
-	pfree(nulls);
-
-	return tuple;
-}
-
-
-static HeapTuple
-PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object)
-{
-	HeapTuple	tuple;
-	Datum	   *values;
-	bool	   *nulls;
-	volatile int i;
-
-	if (info->is_rowtype == 2)
-		PLy_output_tuple_funcs(info, desc);
-	Assert(info->is_rowtype == 1);
-
-	/* Build tuple */
-	values = palloc(sizeof(Datum) * desc->natts);
-	nulls = palloc(sizeof(bool) * desc->natts);
-	for (i = 0; i < desc->natts; ++i)
-	{
-		char	   *key;
-		PyObject   *volatile value;
-		PLyObToDatum *att;
-
-		if (desc->attrs[i]->attisdropped)
-		{
-			values[i] = (Datum) 0;
-			nulls[i] = true;
-			continue;
-		}
-
-		key = NameStr(desc->attrs[i]->attname);
-		value = NULL;
-		att = &info->out.r.atts[i];
-		PG_TRY();
-		{
-			value = PyObject_GetAttrString(object, key);
-			if (value == Py_None)
-			{
-				values[i] = (Datum) NULL;
-				nulls[i] = true;
-			}
-			else if (value)
-			{
-				values[i] = (att->func) (att, -1, value);
-				nulls[i] = false;
-			}
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("attribute \"%s\" does not exist in Python object", key),
-						 errhint("To return null in a column, "
-						   "let the returned object have an attribute named "
-								 "after column with value None.")));
-
-			Py_XDECREF(value);
-			value = NULL;
-		}
-		PG_CATCH();
-		{
-			Py_XDECREF(value);
-			PG_RE_THROW();
-		}
-		PG_END_TRY();
-	}
-
-	tuple = heap_form_tuple(desc, values, nulls);
-	ReleaseTupleDesc(desc);
-	pfree(values);
-	pfree(nulls);
-
-	return tuple;
-}
-
-
-/* initialization, some python variables function declared here */
-
-/* interface to postgresql elog */
-static PyObject *PLy_debug(PyObject *, PyObject *);
-static PyObject *PLy_log(PyObject *, PyObject *);
-static PyObject *PLy_info(PyObject *, PyObject *);
-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_status(PyObject *, PyObject *);
-
-static PyObject *PLy_result_new(void);
-static void PLy_result_dealloc(PyObject *);
-static PyObject *PLy_result_nrows(PyObject *, PyObject *);
-static PyObject *PLy_result_status(PyObject *, PyObject *);
-static Py_ssize_t PLy_result_length(PyObject *);
-static PyObject *PLy_result_item(PyObject *, Py_ssize_t);
-static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t);
-static int	PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *);
-static int	PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);
-
-
-/* handling of SPI operations inside subtransactions */
-static void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner);
-static void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner);
-static void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner);
-
-/* SPI operations */
-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 PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
-static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
-static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
-
-static PyObject *PLy_subtransaction(PyObject *, PyObject *);
-static PyObject *PLy_subtransaction_new(void);
-static void PLy_subtransaction_dealloc(PyObject *);
-static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *);
-static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *);
-
-static PyObject *PLy_cursor(PyObject *self, PyObject *unused);
-static PyObject *PLy_cursor_query(const char *query);
-static PyObject *PLy_cursor_plan(PyObject *ob, PyObject *args);
-static void PLy_cursor_dealloc(PyObject *arg);
-static PyObject *PLy_cursor_iternext(PyObject *self);
-static PyObject *PLy_cursor_fetch(PyObject *self, PyObject *args);
-static PyObject *PLy_cursor_close(PyObject *self, PyObject *unused);
-
-
-static PyMethodDef PLy_plan_methods[] = {
-	{"status", PLy_plan_status, METH_VARARGS, NULL},
-	{NULL, NULL, 0, NULL}
-};
-
-static PyTypeObject PLy_PlanType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"PLyPlan",					/* tp_name */
-	sizeof(PLyPlanObject),		/* tp_size */
-	0,							/* tp_itemsize */
-
-	/*
-	 * methods
-	 */
-	PLy_plan_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,	/* tp_flags */
-	PLy_plan_doc,				/* tp_doc */
-	0,							/* tp_traverse */
-	0,							/* tp_clear */
-	0,							/* tp_richcompare */
-	0,							/* tp_weaklistoffset */
-	0,							/* tp_iter */
-	0,							/* tp_iternext */
-	PLy_plan_methods,			/* tp_tpmethods */
-};
-
-static PySequenceMethods PLy_result_as_sequence = {
-	PLy_result_length,			/* sq_length */
-	NULL,						/* sq_concat */
-	NULL,						/* sq_repeat */
-	PLy_result_item,			/* sq_item */
-	PLy_result_slice,			/* sq_slice */
-	PLy_result_ass_item,		/* sq_ass_item */
-	PLy_result_ass_slice,		/* sq_ass_slice */
-};
-
-static PyMethodDef PLy_result_methods[] = {
-	{"nrows", PLy_result_nrows, METH_VARARGS, NULL},
-	{"status", PLy_result_status, METH_VARARGS, NULL},
-	{NULL, NULL, 0, NULL}
-};
-
-static PyTypeObject PLy_ResultType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"PLyResult",				/* tp_name */
-	sizeof(PLyResultObject),	/* tp_size */
-	0,							/* tp_itemsize */
-
-	/*
-	 * methods
-	 */
-	PLy_result_dealloc,			/* tp_dealloc */
-	0,							/* tp_print */
-	0,							/* 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 */
-	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
-	PLy_result_doc,				/* tp_doc */
-	0,							/* tp_traverse */
-	0,							/* tp_clear */
-	0,							/* tp_richcompare */
-	0,							/* tp_weaklistoffset */
-	0,							/* tp_iter */
-	0,							/* tp_iternext */
-	PLy_result_methods,			/* tp_tpmethods */
-};
-
-static PyMethodDef PLy_subtransaction_methods[] = {
-	{"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL},
-	{"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL},
-	/* user-friendly names for Python <2.6 */
-	{"enter", PLy_subtransaction_enter, METH_VARARGS, NULL},
-	{"exit", PLy_subtransaction_exit, METH_VARARGS, NULL},
-	{NULL, NULL, 0, NULL}
-};
-
-static PyTypeObject PLy_SubtransactionType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"PLySubtransaction",		/* tp_name */
-	sizeof(PLySubtransactionObject),	/* tp_size */
-	0,							/* tp_itemsize */
-
-	/*
-	 * methods
-	 */
-	PLy_subtransaction_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,	/* tp_flags */
-	PLy_subtransaction_doc,		/* tp_doc */
-	0,							/* tp_traverse */
-	0,							/* tp_clear */
-	0,							/* tp_richcompare */
-	0,							/* tp_weaklistoffset */
-	0,							/* tp_iter */
-	0,							/* tp_iternext */
-	PLy_subtransaction_methods, /* tp_tpmethods */
-};
-
-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 */
-};
-
-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},
-
-	/*
-	 * escaping strings
-	 */
-	{"quote_literal", PLy_quote_literal, METH_VARARGS, NULL},
-	{"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
-	{"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
-
-	/*
-	 * create the subtransaction context manager
-	 */
-	{"subtransaction", PLy_subtransaction, METH_NOARGS, NULL},
-
-	/*
-	 * create a cursor
-	 */
-	{"cursor", PLy_cursor, METH_VARARGS, NULL},
-
-	{NULL, NULL, 0, NULL}
-};
-
-static PyMethodDef PLy_exc_methods[] = {
-	{NULL, NULL, 0, NULL}
-};
-
-#if PY_MAJOR_VERSION >= 3
-static PyModuleDef PLy_module = {
-	PyModuleDef_HEAD_INIT,		/* m_base */
-	"plpy",						/* m_name */
-	NULL,						/* m_doc */
-	-1,							/* m_size */
-	PLy_methods,				/* m_methods */
-};
-
-static PyModuleDef PLy_exc_module = {
-	PyModuleDef_HEAD_INIT,		/* m_base */
-	"spiexceptions",			/* m_name */
-	NULL,						/* m_doc */
-	-1,							/* m_size */
-	PLy_exc_methods,			/* m_methods */
-	NULL,						/* m_reload */
-	NULL,						/* m_traverse */
-	NULL,						/* m_clear */
-	NULL						/* m_free */
-};
-#endif
-
-/* 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->values = 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->values)
-		PLy_free(ob->values);
-	if (ob->args)
-	{
-		int			i;
-
-		for (i = 0; i < ob->nargs; i++)
-			PLy_typeinfo_dealloc(&ob->args[i]);
-		PLy_free(ob->args);
-	}
-
-	arg->ob_type->tp_free(arg);
-}
-
-
-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); */
-	}
-	PLy_exception_set(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);
-
-	arg->ob_type->tp_free(arg);
-}
-
-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 Py_ssize_t
-PLy_result_length(PyObject *arg)
-{
-	PLyResultObject *ob = (PLyResultObject *) arg;
-
-	return PyList_Size(ob->rows);
-}
-
-static PyObject *
-PLy_result_item(PyObject *arg, Py_ssize_t 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, Py_ssize_t 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, Py_ssize_t lidx, Py_ssize_t hidx)
-{
-	PLyResultObject *ob = (PLyResultObject *) arg;
-
-	return PyList_GetSlice(ob->rows, lidx, hidx);
-}
-
-static int
-PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice)
-{
-	int			rv;
-	PLyResultObject *ob = (PLyResultObject *) arg;
-
-	rv = PyList_SetSlice(ob->rows, lidx, hidx, slice);
-	return rv;
-}
-
-/*
- * Utilities for running SPI functions in subtransactions.
- *
- * Usage:
- *
- *  MemoryContext oldcontext = CurrentMemoryContext;
- *  ResourceOwner oldowner = CurrentResourceOwner;
- *
- *  PLy_spi_subtransaction_begin(oldcontext, oldowner);
- *  PG_TRY();
- *  {
- *      <call SPI functions>
- *      PLy_spi_subtransaction_commit(oldcontext, oldowner);
- *  }
- *  PG_CATCH();
- *  {
- *      <do cleanup>
- *      PLy_spi_subtransaction_abort(oldcontext, oldowner);
- *      return NULL;
- *  }
- *  PG_END_TRY();
- *
- * These utilities take care of restoring connection to the SPI manager and
- * setting a Python exception in case of an abort.
- */
-static void
-PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner)
-{
-	BeginInternalSubTransaction(NULL);
-	/* Want to run inside function's memory context */
-	MemoryContextSwitchTo(oldcontext);
-}
-
-static void
-PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner)
-{
-	/* Commit the inner transaction, return to outer xact context */
-	ReleaseCurrentSubTransaction();
-	MemoryContextSwitchTo(oldcontext);
-	CurrentResourceOwner = oldowner;
-
-	/*
-	 * AtEOSubXact_SPI() should not have popped any SPI context, but just
-	 * in case it did, make sure we remain connected.
-	 */
-	SPI_restore_connection();
-}
-
-static void
-PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner)
-{
-	ErrorData  *edata;
-	PLyExceptionEntry *entry;
-	PyObject   *exc;
-
-	/* Save error info */
-	MemoryContextSwitchTo(oldcontext);
-	edata = CopyErrorData();
-	FlushErrorState();
-
-	/* Abort the inner transaction */
-	RollbackAndReleaseCurrentSubTransaction();
-	MemoryContextSwitchTo(oldcontext);
-	CurrentResourceOwner = oldowner;
-
-	/*
-	 * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will have
-	 * left us in a disconnected state.  We need this hack to return to
-	 * connected state.
-	 */
-	SPI_restore_connection();
-
-	/* Look up the correct exception */
-	entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
-						HASH_FIND, NULL);
-	/* We really should find it, but just in case have a fallback */
-	Assert(entry != NULL);
-	exc = entry ? entry->exc : PLy_exc_spi_error;
-	/* Make Python raise the exception */
-	PLy_spi_exception_set(exc, edata);
-	FreeErrorData(edata);
-}
-
-
-/* SPI interface */
-static 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;
-}
-
-/* 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;
-
-	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);
-
-	PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan");
-	return NULL;
-}
-
-static PyObject *
-PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
-{
-	volatile int nargs;
-	int			i,
-				rv;
-	PLyPlanObject *plan;
-	volatile MemoryContext oldcontext;
-	volatile ResourceOwner oldowner;
-	PyObject   *ret;
-
-	if (list != NULL)
-	{
-		if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list))
-		{
-			PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument");
-			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, "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;
-	}
-
-	oldcontext = CurrentMemoryContext;
-	oldowner = CurrentResourceOwner;
-
-	PLy_spi_subtransaction_begin(oldcontext, oldowner);
-
-	PG_TRY();
-	{
-		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(list, 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';
-			}
-		}
-
-		rv = SPI_execute_plan(plan->plan, plan->values, nulls,
-							  PLy_curr_procedure->fn_readonly, limit);
-		ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
-
-		if (nargs > 0)
-			pfree(nulls);
-
-		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);
-			}
-		}
-
-		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);
-		}
-	}
-
-	if (rv < 0)
-	{
-		PLy_exception_set(PLy_exc_spi_error,
-						  "SPI_execute_plan failed: %s",
-						  SPI_result_code_string(rv));
-		return NULL;
-	}
-
-	return ret;
-}
-
-static PyObject *
-PLy_spi_execute_query(char *query, long limit)
-{
-	int			rv;
-	volatile MemoryContext oldcontext;
-	volatile ResourceOwner oldowner;
-	PyObject   *ret;
-
-	oldcontext = CurrentMemoryContext;
-	oldowner = CurrentResourceOwner;
-
-	PLy_spi_subtransaction_begin(oldcontext, oldowner);
-
-	PG_TRY();
-	{
-		pg_verifymbstr(query, strlen(query), false);
-		rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
-		ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
-
-		PLy_spi_subtransaction_commit(oldcontext, oldowner);
-	}
-	PG_CATCH();
-	{
-		PLy_spi_subtransaction_abort(oldcontext, oldowner);
-		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 ret;
-}
-
-static PyObject *
-PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
-{
-	PLyResultObject *result;
-	volatile 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);
-				}
-			}
-		}
-		PG_CATCH();
-		{
-			MemoryContextSwitchTo(oldcontext);
-			if (!PyErr_Occurred())
-				PLy_exception_set(PLy_exc_error,
-					   "unrecognized error in PLy_spi_execute_fetch_result");
-			PLy_typeinfo_dealloc(&args);
-			SPI_freetuptable(tuptable);
-			Py_DECREF(result);
-			return NULL;
-		}
-		PG_END_TRY();
-
-		PLy_typeinfo_dealloc(&args);
-		SPI_freetuptable(tuptable);
-	}
-
-	return (PyObject *) result;
-}
-
-/*
- * c = plpy.cursor("select * from largetable")
- * c = plpy.cursor(plan, [])
- */
-static 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;
-}
-
-/* s = plpy.subtransaction() */
-static PyObject *
-PLy_subtransaction(PyObject *self, PyObject *unused)
-{
-	return PLy_subtransaction_new();
-}
-
-/* Allocate and initialize a PLySubtransactionObject */
-static PyObject *
-PLy_subtransaction_new(void)
-{
-	PLySubtransactionObject *ob;
-
-	ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType);
-
-	if (ob == NULL)
-		return NULL;
-
-	ob->started = false;
-	ob->exited = false;
-
-	return (PyObject *) ob;
-}
-
-/* Python requires a dealloc function to be defined */
-static void
-PLy_subtransaction_dealloc(PyObject *subxact)
-{
-}
-
-/*
- * subxact.__enter__() or subxact.enter()
- *
- * Start an explicit subtransaction.  SPI calls within an explicit
- * subtransaction will not start another one, so you can atomically
- * execute many SPI calls and still get a controllable exception if
- * one of them fails.
- */
-static PyObject *
-PLy_subtransaction_enter(PyObject *self, PyObject *unused)
-{
-	PLySubtransactionData *subxactdata;
-	MemoryContext oldcontext;
-	PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
-
-	if (subxact->started)
-	{
-		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered");
-		return NULL;
-	}
-
-	if (subxact->exited)
-	{
-		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
-		return NULL;
-	}
-
-	subxact->started = true;
-	oldcontext = CurrentMemoryContext;
-
-	subxactdata = PLy_malloc(sizeof(*subxactdata));
-	subxactdata->oldcontext = oldcontext;
-	subxactdata->oldowner = CurrentResourceOwner;
-
-	BeginInternalSubTransaction(NULL);
-	/* Do not want to leave the previous memory context */
-	MemoryContextSwitchTo(oldcontext);
-
-	explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
-
-	Py_INCREF(self);
-	return self;
-}
-
-/*
- * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb)
- *
- * Exit an explicit subtransaction. exc_type is an exception type, exc
- * is the exception object, tb is the traceback.  If exc_type is None,
- * commit the subtransactiony, if not abort it.
- *
- * The method signature is chosen to allow subtransaction objects to
- * be used as context managers as described in
- * <http://www.python.org/dev/peps/pep-0343/>.
- */
-static PyObject *
-PLy_subtransaction_exit(PyObject *self, PyObject *args)
-{
-	PyObject   *type;
-	PyObject   *value;
-	PyObject   *traceback;
-	PLySubtransactionData *subxactdata;
-	PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
-
-	if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback))
-		return NULL;
-
-	if (!subxact->started)
-	{
-		PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered");
-		return NULL;
-	}
-
-	if (subxact->exited)
-	{
-		PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
-		return NULL;
-	}
-
-	if (explicit_subtransactions == NIL)
-	{
-		PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from");
-		return NULL;
-	}
-
-	subxact->exited = true;
-
-	if (type != Py_None)
-	{
-		/* Abort the inner transaction */
-		RollbackAndReleaseCurrentSubTransaction();
-	}
-	else
-	{
-		ReleaseCurrentSubTransaction();
-	}
-
-	subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions);
-	explicit_subtransactions = list_delete_first(explicit_subtransactions);
-
-	MemoryContextSwitchTo(subxactdata->oldcontext);
-	CurrentResourceOwner = subxactdata->oldowner;
-	PLy_free(subxactdata);
-
-	/*
-	 * AtEOSubXact_SPI() should not have popped any SPI context, but just in
-	 * case it did, make sure we remain connected.
-	 */
-	SPI_restore_connection();
-
-	Py_INCREF(Py_None);
-	return Py_None;
-}
-
-
-/*
- * language handler and interpreter initialization
- */
-
-/*
- * Add exceptions to the plpy module
- */
-
-/*
- * Add all the autogenerated exceptions as subclasses of SPIError
- */
-static void
-PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
-{
-	int			i;
-
-	for (i = 0; exception_map[i].name != NULL; i++)
-	{
-		bool		found;
-		PyObject   *exc;
-		PLyExceptionEntry *entry;
-		PyObject   *sqlstate;
-		PyObject   *dict = PyDict_New();
-
-		sqlstate = PyString_FromString(unpack_sql_state(exception_map[i].sqlstate));
-		PyDict_SetItemString(dict, "sqlstate", sqlstate);
-		Py_DECREF(sqlstate);
-		exc = PyErr_NewException(exception_map[i].name, base, dict);
-		PyModule_AddObject(mod, exception_map[i].classname, exc);
-		entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
-							HASH_ENTER, &found);
-		entry->exc = exc;
-		Assert(!found);
-	}
-}
-
-static void
-PLy_add_exceptions(PyObject *plpy)
-{
-	PyObject   *excmod;
-	HASHCTL		hash_ctl;
-
-#if PY_MAJOR_VERSION < 3
-	excmod = Py_InitModule("spiexceptions", PLy_exc_methods);
-#else
-	excmod = PyModule_Create(&PLy_exc_module);
-#endif
-	if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
-		PLy_elog(ERROR, "could not add the spiexceptions module");
-
-/*
- * XXX it appears that in some circumstances the reference count of the
- * spiexceptions module drops to zero causing a Python assert failure when
- * the garbage collector visits the module. This has been observed on the
- * buildfarm. To fix this, add an additional ref for the module here.
- *
- * This shouldn't cause a memory leak - we don't want this garbage collected,
- * and this function shouldn't be called more than once per backend.
- */
-	Py_INCREF(excmod);
-
-	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);
-
-	Py_INCREF(PLy_exc_error);
-	PyModule_AddObject(plpy, "Error", PLy_exc_error);
-	Py_INCREF(PLy_exc_fatal);
-	PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal);
-	Py_INCREF(PLy_exc_spi_error);
-	PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error);
-
-	memset(&hash_ctl, 0, sizeof(hash_ctl));
-	hash_ctl.keysize = sizeof(int);
-	hash_ctl.entrysize = sizeof(PLyExceptionEntry);
-	hash_ctl.hash = tag_hash;
-	PLy_spi_exceptions = hash_create("SPI exceptions", 256,
-									 &hash_ctl, HASH_ELEM | HASH_FUNCTION);
-
-	PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
-}
-
-#if PY_MAJOR_VERSION >= 3
-/*
- * Must have external linkage, because PyMODINIT_FUNC does dllexport on
- * Windows-like platforms.
- */
-PyMODINIT_FUNC PyInit_plpy(void);
-
-PyMODINIT_FUNC
-PyInit_plpy(void)
-{
-	PyObject   *m;
-
-	m = PyModule_Create(&PLy_module);
-	if (m == NULL)
-		return NULL;
-
-	PLy_add_exceptions(m);
-
-	return m;
-}
-#endif
-
-
-static const int plpython_python_version = PY_MAJOR_VERSION;
-
-/*
- * _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;
-	const int **version_ptr;
-	HASHCTL		hash_ctl;
-
-	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");
-
-	memset(&hash_ctl, 0, sizeof(hash_ctl));
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
-	hash_ctl.hash = oid_hash;
-	PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
-									  HASH_ELEM | HASH_FUNCTION);
-
-	memset(&hash_ctl, 0, sizeof(hash_ctl));
-	hash_ctl.keysize = sizeof(Oid);
-	hash_ctl.entrysize = sizeof(PLyProcedureEntry);
-	hash_ctl.hash = oid_hash;
-	PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
-									HASH_ELEM | HASH_FUNCTION);
-
-	explicit_subtransactions = NIL;
-
-	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;
-#if PY_MAJOR_VERSION < 3
-	PyObject   *plpy;
-#endif
-
-	/*
-	 * initialize plpy module
-	 */
-	if (PyType_Ready(&PLy_PlanType) < 0)
-		elog(ERROR, "could not initialize PLy_PlanType");
-	if (PyType_Ready(&PLy_ResultType) < 0)
-		elog(ERROR, "could not initialize PLy_ResultType");
-	if (PyType_Ready(&PLy_SubtransactionType) < 0)
-		elog(ERROR, "could not initialize PLy_SubtransactionType");
-	if (PyType_Ready(&PLy_CursorType) < 0)
-		elog(ERROR, "could not initialize PLy_CursorType");
-
-#if PY_MAJOR_VERSION >= 3
-	PyModule_Create(&PLy_module);
-	/* for Python 3 we initialized the exceptions in PyInit_plpy */
-#else
-	plpy = Py_InitModule("plpy", PLy_methods);
-	PLy_add_exceptions(plpy);
-#endif
-
-	/* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
-
-	/*
-	 * 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 initialize 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   *volatile so;
-	char	   *volatile sv;
-	volatile MemoryContext oldcontext;
-
-	if (PyTuple_Size(args) == 1)
-	{
-		/*
-		 * Treat single argument specially to avoid undesirable ('tuple',)
-		 * decoration.
-		 */
-		PyObject   *o;
-
-		PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o);
-		so = PyObject_Str(o);
-	}
-	else
-		so = PyObject_Str(args);
-	if (so == NULL || ((sv = PyString_AsString(so)) == NULL))
-	{
-		level = ERROR;
-		sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
-	}
-
-	oldcontext = CurrentMemoryContext;
-	PG_TRY();
-	{
-		pg_verifymbstr(sv, strlen(sv), false);
-		elog(level, "%s", sv);
-	}
-	PG_CATCH();
-	{
-		ErrorData  *edata;
-
-		MemoryContextSwitchTo(oldcontext);
-		edata = CopyErrorData();
-		FlushErrorState();
-
-		/*
-		 * Note: If sv came from PyString_AsString(), it points into storage
-		 * owned by so.  So free so after using sv.
-		 */
-		Py_XDECREF(so);
-
-		/* Make Python raise the exception */
-		PLy_exception_set(PLy_exc_error, "%s", edata->message);
-		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;
-}
-
-
-static PyObject *
-PLy_quote_literal(PyObject *self, PyObject *args)
-{
-	const char *str;
-	char	   *quoted;
-	PyObject   *ret;
-
-	if (!PyArg_ParseTuple(args, "s", &str))
-		return NULL;
-
-	quoted = quote_literal_cstr(str);
-	ret = PyString_FromString(quoted);
-	pfree(quoted);
-
-	return ret;
-}
-
-static PyObject *
-PLy_quote_nullable(PyObject *self, PyObject *args)
-{
-	const char *str;
-	char	   *quoted;
-	PyObject   *ret;
-
-	if (!PyArg_ParseTuple(args, "z", &str))
-		return NULL;
-
-	if (str == NULL)
-		return PyString_FromString("NULL");
-
-	quoted = quote_literal_cstr(str);
-	ret = PyString_FromString(quoted);
-	pfree(quoted);
-
-	return ret;
-}
-
-static PyObject *
-PLy_quote_ident(PyObject *self, PyObject *args)
-{
-	const char *str;
-	const char *quoted;
-	PyObject   *ret;
-
-	if (!PyArg_ParseTuple(args, "s", &str))
-		return NULL;
-
-	quoted = quote_identifier(str);
-	ret = PyString_FromString(quoted);
-
-	return ret;
-}
-
-
-/*
- * 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 "<unknown procedure>";
-	return proc->proname;
-}
-
-/*
- * Call PyErr_SetString with a vprint interface and translation support
- */
-static 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);
-}
-
-/*
- * The same, pluralized.
- */
-static void
-PLy_exception_set_plural(PyObject *exc,
-						 const char *fmt_singular, const char *fmt_plural,
-						 unsigned long n,...)
-{
-	char		buf[1024];
-	va_list		ap;
-
-	va_start(ap, n);
-	vsnprintf(buf, sizeof(buf),
-			  dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
-			  ap);
-	va_end(ap);
-
-	PyErr_SetString(exc, buf);
-}
-
-/*
- * Raise a SPIError, passing in it more error details, like the
- * internal query and error position.
- */
-static void
-PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
-{
-	PyObject   *args = NULL;
-	PyObject   *spierror = NULL;
-	PyObject   *spidata = NULL;
-
-	args = Py_BuildValue("(s)", edata->message);
-	if (!args)
-		goto failure;
-
-	/* create a new SPI exception with the error message as the parameter */
-	spierror = PyObject_CallObject(excclass, args);
-	if (!spierror)
-		goto failure;
-
-	spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
-							edata->internalquery, edata->internalpos);
-	if (!spidata)
-		goto failure;
-
-	if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
-		goto failure;
-
-	PyErr_SetObject(excclass, spierror);
-
-	Py_DECREF(args);
-	Py_DECREF(spierror);
-	Py_DECREF(spidata);
-	return;
-
-failure:
-	Py_XDECREF(args);
-	Py_XDECREF(spierror);
-	Py_XDECREF(spidata);
-	elog(ERROR, "could not convert SPI error to Python exception");
-}
-
-/* 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.
- */
-static 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 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);
-}
-
-/*
- * 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);
-}
-
-/* python module code */
-
-/* some dumb utility functions */
-static void *
-PLy_malloc(size_t bytes)
-{
-	/* We need our allocations to be long-lived, so use TopMemoryContext */
-	return MemoryContextAlloc(TopMemoryContext, bytes);
-}
-
-static void *
-PLy_malloc0(size_t bytes)
-{
-	void	   *ptr = PLy_malloc(bytes);
-
-	MemSet(ptr, 0, bytes);
-	return ptr;
-}
-
-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)
-{
-	pfree(ptr);
-}
-
-/*
- * Convert a Python unicode object to a Python string/bytes object in
- * PostgreSQL server encoding.	Reference ownership is passed to the
- * caller.
- */
-static PyObject *
-PLyUnicode_Bytes(PyObject *unicode)
-{
-	PyObject   *rv;
-	const char *serverenc;
-
-	/*
-	 * Python understands almost all PostgreSQL encoding names, but it doesn't
-	 * know SQL_ASCII.
-	 */
-	if (GetDatabaseEncoding() == PG_SQL_ASCII)
-		serverenc = "ascii";
-	else
-		serverenc = GetDatabaseEncodingName();
-	rv = PyUnicode_AsEncodedString(unicode, serverenc, "strict");
-	if (rv == NULL)
-		PLy_elog(ERROR, "could not convert Python Unicode object to PostgreSQL server encoding");
-	return rv;
-}
-
-/*
- * Convert a Python unicode object to a C string in PostgreSQL server
- * encoding.  No Python object reference is passed out of this
- * function.  The result is palloc'ed.
- *
- * Note that this function is disguised as PyString_AsString() when
- * using Python 3.	That function retuns a pointer into the internal
- * memory of the argument, which isn't exactly the interface of this
- * function.  But in either case you get a rather short-lived
- * reference that you ought to better leave alone.
- */
-static char *
-PLyUnicode_AsString(PyObject *unicode)
-{
-	PyObject   *o = PLyUnicode_Bytes(unicode);
-	char	   *rv = pstrdup(PyBytes_AsString(o));
-
-	Py_XDECREF(o);
-	return rv;
-}
-
-#if PY_MAJOR_VERSION >= 3
-/*
- * Convert a C string in the PostgreSQL server encoding to a Python
- * unicode object.	Reference ownership is passed to the caller.
- */
-static PyObject *
-PLyUnicode_FromString(const char *s)
-{
-	char	   *utf8string;
-	PyObject   *o;
-
-	utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s,
-													strlen(s),
-													GetDatabaseEncoding(),
-													PG_UTF8);
-
-	o = PyUnicode_FromString(utf8string);
-
-	if (utf8string != s)
-		pfree(utf8string);
-
-	return o;
-}
-#endif   /* PY_MAJOR_VERSION >= 3 */
-
-#if PY_MAJOR_VERSION < 3
-
-/* Define aliases plpython2_call_handler etc */
-Datum		plpython2_call_handler(PG_FUNCTION_ARGS);
-Datum		plpython2_inline_handler(PG_FUNCTION_ARGS);
-Datum		plpython2_validator(PG_FUNCTION_ARGS);
-
-PG_FUNCTION_INFO_V1(plpython2_call_handler);
-
-Datum
-plpython2_call_handler(PG_FUNCTION_ARGS)
-{
-	return plpython_call_handler(fcinfo);
-}
-
-PG_FUNCTION_INFO_V1(plpython2_inline_handler);
-
-Datum
-plpython2_inline_handler(PG_FUNCTION_ARGS)
-{
-	return plpython_inline_handler(fcinfo);
-}
-
-PG_FUNCTION_INFO_V1(plpython2_validator);
-
-Datum
-plpython2_validator(PG_FUNCTION_ARGS)
-{
-	return plpython_validator(fcinfo);
-}
-
-#endif   /* PY_MAJOR_VERSION < 3 */
diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h
new file mode 100644
index 00000000000..ab2983c16db
--- /dev/null
+++ b/src/pl/plpython/plpython.h
@@ -0,0 +1,156 @@
+/*-------------------------------------------------------------------------
+ *
+ * plpython.h - Python as a procedural language for PostgreSQL
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpython/plpython.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PLPYTHON_H
+#define PLPYTHON_H
+
+/*
+ * Include order should be: postgres.h, other postgres headers, plpython.h,
+ * other plpython headers
+ */
+#ifndef POSTGRES_H
+#error postgres.h must be included before plpython.h
+#endif
+
+/*
+ * Undefine some things that get (re)defined in the Python headers. They aren't
+ * used by the PL/Python code, and all PostgreSQL headers should be included
+ * earlier, so this should be pretty safe.
+ */
+#undef _POSIX_C_SOURCE
+#undef _XOPEN_SOURCE
+#undef HAVE_STRERROR
+#undef HAVE_TZNAME
+
+/*
+ * Sometimes python carefully scribbles on our *printf macros.
+ * So we undefine them here and redefine them after it's done its dirty deed.
+ */
+
+#ifdef USE_REPL_SNPRINTF
+#undef snprintf
+#undef vsnprintf
+#endif
+
+#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 <Python.h>
+#undef errcode
+#define _DEBUG
+#elif defined (_MSC_VER)
+#define errcode __msvc_errcode
+#include <Python.h>
+#undef errcode
+#else
+#include <Python.h>
+#endif
+
+/*
+ * Py_ssize_t compat for Python <= 2.4
+ */
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
+/*
+ * PyBool_FromLong is supported from 2.3.
+ */
+#if PY_VERSION_HEX < 0x02030000
+#define PyBool_FromLong(x) PyInt_FromLong(x)
+#endif
+
+/*
+ * Python 2/3 strings/unicode/bytes handling.  Python 2 has strings
+ * and unicode, Python 3 has strings, which are unicode on the C
+ * level, and bytes.  The porting convention, which is similarly used
+ * in Python 2.6, is that "Unicode" is always unicode, and "Bytes" are
+ * bytes in Python 3 and strings in Python 2.  Since we keep
+ * supporting Python 2 and its usual strings, we provide a
+ * compatibility layer for Python 3 that when asked to convert a C
+ * string to a Python string it converts the C string from the
+ * PostgreSQL server encoding to a Python Unicode object.
+ */
+
+#if PY_VERSION_HEX < 0x02060000
+/* This is exactly the compatibility layer that Python 2.6 uses. */
+#define PyBytes_AsString PyString_AsString
+#define PyBytes_FromStringAndSize PyString_FromStringAndSize
+#define PyBytes_Size PyString_Size
+#define PyObject_Bytes PyObject_Str
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+#define PyString_Check(x) 0
+#define PyString_AsString(x) PLyUnicode_AsString(x)
+#define PyString_FromString(x) PLyUnicode_FromString(x)
+#endif
+
+/*
+ * Python 3 only has long.
+ */
+#if PY_MAJOR_VERSION >= 3
+#define PyInt_FromLong(x) PyLong_FromLong(x)
+#define PyInt_AsLong(x) PyLong_AsLong(x)
+#endif
+
+/*
+ * PyVarObject_HEAD_INIT was added in Python 2.6.  Its use is
+ * necessary to handle both Python 2 and 3.  This replacement
+ * definition is for Python <=2.5
+ */
+#ifndef PyVarObject_HEAD_INIT
+#define PyVarObject_HEAD_INIT(type, size)		\
+		PyObject_HEAD_INIT(type) size,
+#endif
+
+/* Python 3 removed the Py_TPFLAGS_HAVE_ITER flag */
+#if PY_MAJOR_VERSION >= 3
+#define Py_TPFLAGS_HAVE_ITER 0
+#endif
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("plpython")
+
+#include <compile.h>
+#include <eval.h>
+
+/* put back our snprintf and vsnprintf */
+#ifdef USE_REPL_SNPRINTF
+#ifdef snprintf
+#undef snprintf
+#endif
+#ifdef vsnprintf
+#undef vsnprintf
+#endif
+#ifdef __GNUC__
+#define vsnprintf(...)  pg_vsnprintf(__VA_ARGS__)
+#define snprintf(...)   pg_snprintf(__VA_ARGS__)
+#else
+#define vsnprintf               pg_vsnprintf
+#define snprintf                pg_snprintf
+#endif   /* __GNUC__ */
+#endif   /* USE_REPL_SNPRINTF */
+
+/*
+ * Used throughout, and also by the Python 2/3 porting layer, so it's easier to
+ * just include it everywhere.
+ */
+#include "plpy_util.h"
+
+#endif   /* PLPYTHON_H */
-- 
GitLab