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