]> granicus.if.org Git - postgresql/commitdiff
Split plpython.c into smaller pieces
authorPeter Eisentraut <peter_e@gmx.net>
Sun, 18 Dec 2011 19:14:16 +0000 (21:14 +0200)
committerPeter Eisentraut <peter_e@gmx.net>
Sun, 18 Dec 2011 19:24:00 +0000 (21:24 +0200)
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

28 files changed:
src/pl/plpython/Makefile
src/pl/plpython/nls.mk
src/pl/plpython/plpy_cursorobject.c [new file with mode: 0644]
src/pl/plpython/plpy_cursorobject.h [new file with mode: 0644]
src/pl/plpython/plpy_elog.c [new file with mode: 0644]
src/pl/plpython/plpy_elog.h [new file with mode: 0644]
src/pl/plpython/plpy_exec.c [new file with mode: 0644]
src/pl/plpython/plpy_exec.h [new file with mode: 0644]
src/pl/plpython/plpy_main.c [new file with mode: 0644]
src/pl/plpython/plpy_main.h [new file with mode: 0644]
src/pl/plpython/plpy_planobject.c [new file with mode: 0644]
src/pl/plpython/plpy_planobject.h [new file with mode: 0644]
src/pl/plpython/plpy_plpymodule.c [new file with mode: 0644]
src/pl/plpython/plpy_plpymodule.h [new file with mode: 0644]
src/pl/plpython/plpy_procedure.c [new file with mode: 0644]
src/pl/plpython/plpy_procedure.h [new file with mode: 0644]
src/pl/plpython/plpy_resultobject.c [new file with mode: 0644]
src/pl/plpython/plpy_resultobject.h [new file with mode: 0644]
src/pl/plpython/plpy_spi.c [new file with mode: 0644]
src/pl/plpython/plpy_spi.h [new file with mode: 0644]
src/pl/plpython/plpy_subxactobject.c [new file with mode: 0644]
src/pl/plpython/plpy_subxactobject.h [new file with mode: 0644]
src/pl/plpython/plpy_typeio.c [new file with mode: 0644]
src/pl/plpython/plpy_typeio.h [new file with mode: 0644]
src/pl/plpython/plpy_util.c [new file with mode: 0644]
src/pl/plpython/plpy_util.h [new file with mode: 0644]
src/pl/plpython/plpython.c [deleted file]
src/pl/plpython/plpython.h [new file with mode: 0644]

index 1b1259feefb557174d905b00b282ebcb005fce12..12ce26e241f71e1140fc05be46cd55692dc70d92 100644 (file)
@@ -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 $< > $@
index 27d5e7cea8f8cdbbcad340eaeabf89d3165ed602..94d406d0a983315ffaa4eeb760fe748eefd584d6 100644 (file)
@@ -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 (file)
index 0000000..48a7727
--- /dev/null
@@ -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 (file)
index 0000000..706134e
--- /dev/null
@@ -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 (file)
index 0000000..0ff55ac
--- /dev/null
@@ -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 (file)
index 0000000..eafc6e4
--- /dev/null
@@ -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 (file)
index 0000000..7724f3f
--- /dev/null
@@ -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 (file)
index 0000000..86ceba1
--- /dev/null
@@ -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 (file)
index 0000000..03c03d1
--- /dev/null
@@ -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 (file)
index 0000000..a71aead
--- /dev/null
@@ -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 (file)
index 0000000..01b40d1
--- /dev/null
@@ -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 (file)
index 0000000..959813a
--- /dev/null
@@ -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 (file)
index 0000000..e911107
--- /dev/null
@@ -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 (file)
index 0000000..930ecfd
--- /dev/null
@@ -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 (file)
index 0000000..b4f2abe
--- /dev/null
@@ -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 (file)
index 0000000..632b975
--- /dev/null
@@ -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 (file)
index 0000000..e1b8926
--- /dev/null
@@ -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 (file)
index 0000000..719828a
--- /dev/null
@@ -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 (file)
index 0000000..5e3099e
--- /dev/null
@@ -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 (file)
index 0000000..c59482a
--- /dev/null
@@ -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 (file)
index 0000000..6c3cc69
--- /dev/null
@@ -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 (file)
index 0000000..0db8aa9
--- /dev/null
@@ -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 (file)
index 0000000..cd6a46d
--- /dev/null
@@ -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 (file)
index 0000000..6708bf9
--- /dev/null
@@ -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 (file)
index 0000000..414b9d5
--- /dev/null
@@ -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 (file)
index 0000000..237d6c5
--- /dev/null
@@ -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 (file)
index dce8ff2..0000000
+++ /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 (file)
index 0000000..ab2983c
--- /dev/null
@@ -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 */