From 147c2482542868d1f9dcf7d2ecfeac58d845335c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Sun, 18 Dec 2011 21:14:16 +0200 Subject: [PATCH] Split plpython.c into smaller pieces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This moves the code around from one huge file into hopefully logical and more manageable modules. For the most part, the code itself was not touched, except: PLy_function_handler and PLy_trigger_handler were renamed to PLy_exec_function and PLy_exec_trigger, because they were not actually handlers in the PL handler sense, and it makes the naming more similar to the way PL/pgSQL is organized. The initialization of the procedure caches was separated into a new function init_procedure_caches to keep the hash tables private to plpy_procedures.c. Jan UrbaƄski and Peter Eisentraut --- src/pl/plpython/Makefile | 16 +- src/pl/plpython/nls.mk | 3 +- src/pl/plpython/plpy_cursorobject.c | 492 +++ src/pl/plpython/plpy_cursorobject.h | 22 + src/pl/plpython/plpy_elog.c | 428 ++ src/pl/plpython/plpy_elog.h | 24 + src/pl/plpython/plpy_exec.c | 859 ++++ src/pl/plpython/plpy_exec.h | 13 + src/pl/plpython/plpy_main.c | 325 ++ src/pl/plpython/plpy_main.h | 13 + src/pl/plpython/plpy_planobject.c | 128 + src/pl/plpython/plpy_planobject.h | 26 + src/pl/plpython/plpy_plpymodule.c | 417 ++ src/pl/plpython/plpy_plpymodule.h | 19 + src/pl/plpython/plpy_procedure.c | 531 +++ src/pl/plpython/plpy_procedure.h | 52 + src/pl/plpython/plpy_resultobject.c | 180 + src/pl/plpython/plpy_resultobject.h | 20 + src/pl/plpython/plpy_spi.c | 559 +++ src/pl/plpython/plpy_spi.h | 25 + src/pl/plpython/plpy_subxactobject.c | 217 + src/pl/plpython/plpy_subxactobject.h | 29 + src/pl/plpython/plpy_typeio.c | 1038 +++++ src/pl/plpython/plpy_typeio.h | 107 + src/pl/plpython/plpy_util.c | 125 + src/pl/plpython/plpy_util.h | 21 + src/pl/plpython/plpython.c | 5439 -------------------------- src/pl/plpython/plpython.h | 156 + 28 files changed, 5842 insertions(+), 5442 deletions(-) create mode 100644 src/pl/plpython/plpy_cursorobject.c create mode 100644 src/pl/plpython/plpy_cursorobject.h create mode 100644 src/pl/plpython/plpy_elog.c create mode 100644 src/pl/plpython/plpy_elog.h create mode 100644 src/pl/plpython/plpy_exec.c create mode 100644 src/pl/plpython/plpy_exec.h create mode 100644 src/pl/plpython/plpy_main.c create mode 100644 src/pl/plpython/plpy_main.h create mode 100644 src/pl/plpython/plpy_planobject.c create mode 100644 src/pl/plpython/plpy_planobject.h create mode 100644 src/pl/plpython/plpy_plpymodule.c create mode 100644 src/pl/plpython/plpy_plpymodule.h create mode 100644 src/pl/plpython/plpy_procedure.c create mode 100644 src/pl/plpython/plpy_procedure.h create mode 100644 src/pl/plpython/plpy_resultobject.c create mode 100644 src/pl/plpython/plpy_resultobject.h create mode 100644 src/pl/plpython/plpy_spi.c create mode 100644 src/pl/plpython/plpy_spi.h create mode 100644 src/pl/plpython/plpy_subxactobject.c create mode 100644 src/pl/plpython/plpy_subxactobject.h create mode 100644 src/pl/plpython/plpy_typeio.c create mode 100644 src/pl/plpython/plpy_typeio.h create mode 100644 src/pl/plpython/plpy_util.c create mode 100644 src/pl/plpython/plpy_util.h delete mode 100644 src/pl/plpython/plpython.c create mode 100644 src/pl/plpython/plpython.h diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 1b1259feef..12ce26e241 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -38,7 +38,19 @@ rpathdir = $(python_libdir) NAME = plpython$(python_majorversion) -OBJS = plpython.o +OBJS = \ + plpy_cursorobject.o \ + plpy_elog.o \ + plpy_exec.o \ + plpy_main.o \ + plpy_planobject.o \ + plpy_plpymodule.o \ + plpy_procedure.o \ + plpy_resultobject.o \ + plpy_spi.o \ + plpy_subxactobject.o \ + plpy_typeio.o \ + plpy_util.o DATA = $(NAME)u.control $(NAME)u--1.0.sql $(NAME)u--unpackaged--1.0.sql ifeq ($(python_majorversion),2) @@ -177,7 +189,7 @@ endif # can't build # distprep and maintainer-clean rules should be run even if we can't build. # Force this dependency to be known even without dependency info built: -plpython.o: spiexceptions.h +plpython_plpy.o: spiexceptions.h spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl $(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@ diff --git a/src/pl/plpython/nls.mk b/src/pl/plpython/nls.mk index 27d5e7cea8..94d406d0a9 100644 --- a/src/pl/plpython/nls.mk +++ b/src/pl/plpython/nls.mk @@ -1,7 +1,8 @@ # src/pl/plpython/nls.mk CATALOG_NAME = plpython AVAIL_LANGUAGES = de es fr it ja pt_BR ro tr zh_CN zh_TW -GETTEXT_FILES = plpython.c +GETTEXT_FILES = plpy_cursorobject.c plpy_elog.c plpy_exec.c plpy_main.c plpy_planobject.c plpy_plpymodule.c \ + plpy_procedure.c plpy_resultobject.c plpy_spi.c plpy_subxactobject.c plpy_typeio.c plpy_util.c GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) PLy_elog:2 PLy_exception_set:2 PLy_exception_set_plural:2,3 GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS) \ PLy_elog:2:c-format \ diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c new file mode 100644 index 0000000000..48a7727b79 --- /dev/null +++ b/src/pl/plpython/plpy_cursorobject.c @@ -0,0 +1,492 @@ +/* + * the PLyCursor class + * + * src/pl/plpython/plpy_cursorobject.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "mb/pg_wchar.h" + +#include "plpython.h" + +#include "plpy_cursorobject.h" + +#include "plpy_elog.h" +#include "plpy_planobject.h" +#include "plpy_procedure.h" +#include "plpy_resultobject.h" +#include "plpy_spi.h" + + +static PyObject *PLy_cursor_query(const char *); +static PyObject *PLy_cursor_plan(PyObject *, PyObject *); +static void PLy_cursor_dealloc(PyObject *); +static PyObject *PLy_cursor_iternext(PyObject *); +static PyObject *PLy_cursor_fetch(PyObject *, PyObject *); +static PyObject *PLy_cursor_close(PyObject *, PyObject *); + +static char PLy_cursor_doc[] = { + "Wrapper around a PostgreSQL cursor" +}; + +static PyMethodDef PLy_cursor_methods[] = { + {"fetch", PLy_cursor_fetch, METH_VARARGS, NULL}, + {"close", PLy_cursor_close, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_CursorType = { + PyVarObject_HEAD_INIT(NULL, 0) + "PLyCursor", /* tp_name */ + sizeof(PLyCursorObject), /* tp_size */ + 0, /* tp_itemsize */ + + /* + * methods + */ + PLy_cursor_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ + PLy_cursor_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + PLy_cursor_iternext, /* tp_iternext */ + PLy_cursor_methods, /* tp_tpmethods */ +}; + +void +PLy_cursor_init_type(void) +{ + if (PyType_Ready(&PLy_CursorType) < 0) + elog(ERROR, "could not initialize PLy_CursorType"); +} + +PyObject * +PLy_cursor(PyObject *self, PyObject *args) +{ + char *query; + PyObject *plan; + PyObject *planargs = NULL; + + if (PyArg_ParseTuple(args, "s", &query)) + return PLy_cursor_query(query); + + PyErr_Clear(); + + if (PyArg_ParseTuple(args, "O|O", &plan, &planargs)) + return PLy_cursor_plan(plan, planargs); + + PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan"); + return NULL; +} + + +static PyObject * +PLy_cursor_query(const char *query) +{ + PLyCursorObject *cursor; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + + if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) + return NULL; + cursor->portalname = NULL; + cursor->closed = false; + PLy_typeinfo_init(&cursor->result); + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPIPlanPtr plan; + Portal portal; + + pg_verifymbstr(query, strlen(query), false); + + plan = SPI_prepare(query, 0, NULL); + if (plan == NULL) + elog(ERROR, "SPI_prepare failed: %s", + SPI_result_code_string(SPI_result)); + + portal = SPI_cursor_open(NULL, plan, NULL, NULL, + PLy_curr_procedure->fn_readonly); + SPI_freeplan(plan); + + if (portal == NULL) + elog(ERROR, "SPI_cursor_open() failed:%s", + SPI_result_code_string(SPI_result)); + + cursor->portalname = PLy_strdup(portal->name); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + Assert(cursor->portalname != NULL); + return (PyObject *) cursor; +} + +static PyObject * +PLy_cursor_plan(PyObject *ob, PyObject *args) +{ + PLyCursorObject *cursor; + volatile int nargs; + int i; + PLyPlanObject *plan; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + + if (args) + { + if (!PySequence_Check(args) || PyString_Check(args) || PyUnicode_Check(args)) + { + PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument"); + return NULL; + } + nargs = PySequence_Length(args); + } + else + nargs = 0; + + plan = (PLyPlanObject *) ob; + + if (nargs != plan->nargs) + { + char *sv; + PyObject *so = PyObject_Str(args); + + if (!so) + PLy_elog(ERROR, "could not execute plan"); + sv = PyString_AsString(so); + PLy_exception_set_plural(PyExc_TypeError, + "Expected sequence of %d argument, got %d: %s", + "Expected sequence of %d arguments, got %d: %s", + plan->nargs, + plan->nargs, nargs, sv); + Py_DECREF(so); + + return NULL; + } + + if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) + return NULL; + cursor->portalname = NULL; + cursor->closed = false; + PLy_typeinfo_init(&cursor->result); + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + Portal portal; + char *volatile nulls; + volatile int j; + + if (nargs > 0) + nulls = palloc(nargs * sizeof(char)); + else + nulls = NULL; + + for (j = 0; j < nargs; j++) + { + PyObject *elem; + + elem = PySequence_GetItem(args, j); + if (elem != Py_None) + { + PG_TRY(); + { + plan->values[j] = + plan->args[j].out.d.func(&(plan->args[j].out.d), + -1, + elem); + } + PG_CATCH(); + { + Py_DECREF(elem); + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(elem); + nulls[j] = ' '; + } + else + { + Py_DECREF(elem); + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), + NULL, + plan->args[j].out.d.typioparam, + -1); + nulls[j] = 'n'; + } + } + + portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, + PLy_curr_procedure->fn_readonly); + if (portal == NULL) + elog(ERROR, "SPI_cursor_open() failed:%s", + SPI_result_code_string(SPI_result)); + + cursor->portalname = PLy_strdup(portal->name); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + int k; + + /* cleanup plan->values array */ + for (k = 0; k < nargs; k++) + { + if (!plan->args[k].out.d.typbyval && + (plan->values[k] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); + } + } + + Py_DECREF(cursor); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + for (i = 0; i < nargs; i++) + { + if (!plan->args[i].out.d.typbyval && + (plan->values[i] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[i])); + plan->values[i] = PointerGetDatum(NULL); + } + } + + Assert(cursor->portalname != NULL); + return (PyObject *) cursor; +} + +static void +PLy_cursor_dealloc(PyObject *arg) +{ + PLyCursorObject *cursor; + Portal portal; + + cursor = (PLyCursorObject *) arg; + + if (!cursor->closed) + { + portal = GetPortalByName(cursor->portalname); + + if (PortalIsValid(portal)) + SPI_cursor_close(portal); + } + + PLy_free(cursor->portalname); + cursor->portalname = NULL; + + PLy_typeinfo_dealloc(&cursor->result); + arg->ob_type->tp_free(arg); +} + +static PyObject * +PLy_cursor_iternext(PyObject *self) +{ + PLyCursorObject *cursor; + PyObject *ret; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + Portal portal; + + cursor = (PLyCursorObject *) self; + + if (cursor->closed) + { + PLy_exception_set(PyExc_ValueError, "iterating a closed cursor"); + return NULL; + } + + portal = GetPortalByName(cursor->portalname); + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "iterating a cursor in an aborted subtransaction"); + return NULL; + } + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPI_cursor_fetch(portal, true, 1); + if (SPI_processed == 0) + { + PyErr_SetNone(PyExc_StopIteration); + ret = NULL; + } + else + { + if (cursor->result.is_rowtype != 1) + PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); + + ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], + SPI_tuptable->tupdesc); + } + + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + return ret; +} + +static PyObject * +PLy_cursor_fetch(PyObject *self, PyObject *args) +{ + PLyCursorObject *cursor; + int count; + PLyResultObject *ret; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + Portal portal; + + if (!PyArg_ParseTuple(args, "i", &count)) + return NULL; + + cursor = (PLyCursorObject *) self; + + if (cursor->closed) + { + PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor"); + return NULL; + } + + portal = GetPortalByName(cursor->portalname); + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "iterating a cursor in an aborted subtransaction"); + return NULL; + } + + ret = (PLyResultObject *) PLy_result_new(); + if (ret == NULL) + return NULL; + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPI_cursor_fetch(portal, true, count); + + if (cursor->result.is_rowtype != 1) + PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); + + Py_DECREF(ret->status); + ret->status = PyInt_FromLong(SPI_OK_FETCH); + + Py_DECREF(ret->nrows); + ret->nrows = PyInt_FromLong(SPI_processed); + + if (SPI_processed != 0) + { + int i; + + Py_DECREF(ret->rows); + ret->rows = PyList_New(SPI_processed); + + for (i = 0; i < SPI_processed; i++) + { + PyObject *row = PLyDict_FromTuple(&cursor->result, + SPI_tuptable->vals[i], + SPI_tuptable->tupdesc); + PyList_SetItem(ret->rows, i, row); + } + } + + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + return (PyObject *) ret; +} + +static PyObject * +PLy_cursor_close(PyObject *self, PyObject *unused) +{ + PLyCursorObject *cursor = (PLyCursorObject *) self; + + if (!cursor->closed) + { + Portal portal = GetPortalByName(cursor->portalname); + + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "closing a cursor in an aborted subtransaction"); + return NULL; + } + + SPI_cursor_close(portal); + cursor->closed = true; + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h new file mode 100644 index 0000000000..706134ea2c --- /dev/null +++ b/src/pl/plpython/plpy_cursorobject.h @@ -0,0 +1,22 @@ +/* + * src/pl/plpython/plpy_cursorobject.h + */ + +#ifndef PLPY_CURSOROBJECT_H +#define PLPY_CURSOROBJECT_H + +#include "plpy_typeio.h" + + +typedef struct PLyCursorObject +{ + PyObject_HEAD + char *portalname; + PLyTypeInfo result; + bool closed; +} PLyCursorObject; + +extern void PLy_cursor_init_type(void); +extern PyObject *PLy_cursor(PyObject *, PyObject *); + +#endif /* PLPY_CURSOROBJECT_H */ diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 0000000000..0ff55ac8bd --- /dev/null +++ b/src/pl/plpython/plpy_elog.c @@ -0,0 +1,428 @@ +/* + * reporting Python exceptions as PostgreSQL errors + * + * src/pl/plpython/plpy_elog.c + */ + +#include "postgres.h" + +#include "lib/stringinfo.h" + +#include "plpython.h" + +#include "plpy_elog.h" + +#include "plpy_procedure.h" + + +PyObject *PLy_exc_error = NULL; +PyObject *PLy_exc_fatal = NULL; +PyObject *PLy_exc_spi_error = NULL; + + +static void PLy_traceback(char **, char **, int *); +static void PLy_get_spi_error_data(PyObject *, int *, char **, + char **, char **, int *); +static char * get_source_line(const char *, int); + + +/* + * Emit a PG error or notice, together with any available info about + * the current Python error, previously set by PLy_exception_set(). + * This should be used to propagate Python errors into PG. If fmt is + * NULL, the Python error becomes the primary error message, otherwise + * it becomes the detail. If there is a Python traceback, it is put + * in the context. + */ +void +PLy_elog(int elevel, const char *fmt,...) +{ + char *xmsg; + char *tbmsg; + int tb_depth; + StringInfoData emsg; + PyObject *exc, + *val, + *tb; + const char *primary = NULL; + int sqlerrcode = 0; + char *detail = NULL; + char *hint = NULL; + char *query = NULL; + int position = 0; + + PyErr_Fetch(&exc, &val, &tb); + if (exc != NULL) + { + if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) + PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); + else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) + elevel = FATAL; + } + PyErr_Restore(exc, val, tb); + + PLy_traceback(&xmsg, &tbmsg, &tb_depth); + + if (fmt) + { + initStringInfo(&emsg); + for (;;) + { + va_list ap; + bool success; + + va_start(ap, fmt); + success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + if (success) + break; + enlargeStringInfo(&emsg, emsg.maxlen); + } + primary = emsg.data; + + /* Since we have a format string, we cannot have a SPI detail. */ + Assert(detail == NULL); + + /* If there's an exception message, it goes in the detail. */ + if (xmsg) + detail = xmsg; + } + else + { + if (xmsg) + primary = xmsg; + } + + PG_TRY(); + { + ereport(elevel, + (errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR), + errmsg_internal("%s", primary ? primary : "no exception data"), + (detail) ? errdetail_internal("%s", detail) : 0, + (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, + (hint) ? errhint("%s", hint) : 0, + (query) ? internalerrquery(query) : 0, + (position) ? internalerrposition(position) : 0)); + } + PG_CATCH(); + { + if (fmt) + pfree(emsg.data); + if (xmsg) + pfree(xmsg); + if (tbmsg) + pfree(tbmsg); + PG_RE_THROW(); + } + PG_END_TRY(); + + if (fmt) + pfree(emsg.data); + if (xmsg) + pfree(xmsg); + if (tbmsg) + pfree(tbmsg); +} + +/* + * Extract a Python traceback from the current exception. + * + * The exception error message is returned in xmsg, the traceback in + * tbmsg (both as palloc'd strings) and the traceback depth in + * tb_depth. + */ +static void +PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) +{ + PyObject *e, + *v, + *tb; + PyObject *e_type_o; + PyObject *e_module_o; + char *e_type_s = NULL; + char *e_module_s = NULL; + PyObject *vob = NULL; + char *vstr; + StringInfoData xstr; + StringInfoData tbstr; + + /* + * get the current exception + */ + PyErr_Fetch(&e, &v, &tb); + + /* + * oops, no exception, return + */ + if (e == NULL) + { + *xmsg = NULL; + *tbmsg = NULL; + *tb_depth = 0; + + return; + } + + PyErr_NormalizeException(&e, &v, &tb); + + /* + * Format the exception and its value and put it in xmsg. + */ + + e_type_o = PyObject_GetAttrString(e, "__name__"); + e_module_o = PyObject_GetAttrString(e, "__module__"); + if (e_type_o) + e_type_s = PyString_AsString(e_type_o); + if (e_type_s) + e_module_s = PyString_AsString(e_module_o); + + if (v && ((vob = PyObject_Str(v)) != NULL)) + vstr = PyString_AsString(vob); + else + vstr = "unknown"; + + initStringInfo(&xstr); + if (!e_type_s || !e_module_s) + { + if (PyString_Check(e)) + /* deprecated string exceptions */ + appendStringInfoString(&xstr, PyString_AsString(e)); + else + /* shouldn't happen */ + appendStringInfoString(&xstr, "unrecognized exception"); + } + /* mimics behavior of traceback.format_exception_only */ + else if (strcmp(e_module_s, "builtins") == 0 + || strcmp(e_module_s, "__main__") == 0 + || strcmp(e_module_s, "exceptions") == 0) + appendStringInfo(&xstr, "%s", e_type_s); + else + appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); + appendStringInfo(&xstr, ": %s", vstr); + + *xmsg = xstr.data; + + /* + * Now format the traceback and put it in tbmsg. + */ + + *tb_depth = 0; + initStringInfo(&tbstr); + /* Mimick Python traceback reporting as close as possible. */ + appendStringInfoString(&tbstr, "Traceback (most recent call last):"); + while (tb != NULL && tb != Py_None) + { + PyObject *volatile tb_prev = NULL; + PyObject *volatile frame = NULL; + PyObject *volatile code = NULL; + PyObject *volatile name = NULL; + PyObject *volatile lineno = NULL; + PyObject *volatile filename = NULL; + + PG_TRY(); + { + lineno = PyObject_GetAttrString(tb, "tb_lineno"); + if (lineno == NULL) + elog(ERROR, "could not get line number from Python traceback"); + + frame = PyObject_GetAttrString(tb, "tb_frame"); + if (frame == NULL) + elog(ERROR, "could not get frame from Python traceback"); + + code = PyObject_GetAttrString(frame, "f_code"); + if (code == NULL) + elog(ERROR, "could not get code object from Python frame"); + + name = PyObject_GetAttrString(code, "co_name"); + if (name == NULL) + elog(ERROR, "could not get function name from Python code object"); + + filename = PyObject_GetAttrString(code, "co_filename"); + if (filename == NULL) + elog(ERROR, "could not get file name from Python code object"); + } + PG_CATCH(); + { + Py_XDECREF(frame); + Py_XDECREF(code); + Py_XDECREF(name); + Py_XDECREF(lineno); + Py_XDECREF(filename); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* The first frame always points at , 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 . + */ + if (*tb_depth == 1) + fname = ""; + 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 "" as the + * filename + */ + if (PLy_curr_procedure && plain_filename != NULL && + strcmp(plain_filename, "") == 0) + { + /* + * If we know the current procedure, append the exact line + * from the source, again mimicking Python's traceback.py + * module behavior. We could store the already line-split + * source to avoid splitting it every time, but producing a + * traceback is not the most important scenario to optimize + * for. But we do not go as far as traceback.py in reading + * the source of imported modules. + */ + line = get_source_line(PLy_curr_procedure->src, plain_lineno); + if (line) + { + appendStringInfo(&tbstr, "\n %s", line); + pfree(line); + } + } + } + + Py_DECREF(frame); + Py_DECREF(code); + Py_DECREF(name); + Py_DECREF(lineno); + Py_DECREF(filename); + + /* Release the current frame and go to the next one. */ + tb_prev = tb; + tb = PyObject_GetAttrString(tb, "tb_next"); + Assert(tb_prev != Py_None); + Py_DECREF(tb_prev); + if (tb == NULL) + elog(ERROR, "could not traverse Python traceback"); + (*tb_depth)++; + } + + /* Return the traceback. */ + *tbmsg = tbstr.data; + + Py_XDECREF(e_type_o); + Py_XDECREF(e_module_o); + Py_XDECREF(vob); + Py_XDECREF(v); + Py_DECREF(e); +} + +/* + * Extract the error data from a SPIError + */ +static void +PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position) +{ + PyObject *spidata = NULL; + + spidata = PyObject_GetAttrString(exc, "spidata"); + if (!spidata) + goto cleanup; + + if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position)) + goto cleanup; + +cleanup: + PyErr_Clear(); + /* no elog here, we simply won't report the errhint, errposition etc */ + Py_XDECREF(spidata); +} + +/* + * Get the given source line as a palloc'd string + */ +static char * +get_source_line(const char *src, int lineno) +{ + const char *s = NULL; + const char *next = src; + int current = 0; + + while (current < lineno) + { + s = next; + next = strchr(s + 1, '\n'); + current++; + if (next == NULL) + break; + } + + if (current != lineno) + return NULL; + + while (*s && isspace((unsigned char) *s)) + s++; + + if (next == NULL) + return pstrdup(s); + + /* + * Sanity check, next < s if the line was all-whitespace, which should + * never happen if Python reported a frame created on that line, but check + * anyway. + */ + if (next < s) + return NULL; + + return pnstrdup(s, next - s); +} + + +/* call PyErr_SetString with a vprint interface and translation support */ +void +PLy_exception_set(PyObject *exc, const char *fmt,...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + + PyErr_SetString(exc, buf); +} + +/* same, with pluralized message */ +void +PLy_exception_set_plural(PyObject *exc, + const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, n); + vsnprintf(buf, sizeof(buf), + dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), + ap); + va_end(ap); + + PyErr_SetString(exc, buf); +} diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h new file mode 100644 index 0000000000..eafc6e4e10 --- /dev/null +++ b/src/pl/plpython/plpy_elog.h @@ -0,0 +1,24 @@ +/* + * src/pl/plpython/plpy_elog.h + */ + +#ifndef PLPY_ELOG_H +#define PLPY_ELOG_H + +/* global exception classes */ +extern PyObject *PLy_exc_error; +extern PyObject *PLy_exc_fatal; +extern PyObject *PLy_exc_spi_error; + +extern void PLy_elog(int, const char *,...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); + +extern void PLy_exception_set(PyObject *, const char *,...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); + +extern void PLy_exception_set_plural(PyObject *, const char *, const char *, + unsigned long n,...) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 5))) +__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 5))); + +#endif /* PLPY_ELOG_H */ diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c new file mode 100644 index 0000000000..7724f3f0cd --- /dev/null +++ b/src/pl/plpython/plpy_exec.c @@ -0,0 +1,859 @@ +/* + * executing Python code + * + * src/pl/plpython/plpy_exec.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/typcache.h" + +#include "plpython.h" + +#include "plpy_exec.h" + +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_procedure.h" +#include "plpy_subxactobject.h" + + +static PyObject *PLy_function_build_args(FunctionCallInfo, PLyProcedure *); +static void PLy_function_delete_args(PLyProcedure *); +static void plpython_return_error_callback(void *); + +static PyObject *PLy_trigger_build_args(FunctionCallInfo, PLyProcedure *, + HeapTuple *); +static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *, + TriggerData *, HeapTuple); +static void plpython_trigger_error_callback(void *); + +static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *); +static void PLy_abort_open_subtransactions(int); + + +/* function subhandler */ +Datum +PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + Datum rv; + PyObject *volatile plargs = NULL; + PyObject *volatile plrv = NULL; + ErrorContextCallback plerrcontext; + + PG_TRY(); + { + if (!proc->is_setof || proc->setof == NULL) + { + /* + * Simple type returning function or first time for SETOF + * function: actually execute the function. + */ + plargs = PLy_function_build_args(fcinfo, proc); + plrv = PLy_procedure_call(proc, "args", plargs); + if (!proc->is_setof) + { + /* + * SETOF function parameters will be deleted when last row is + * returned + */ + PLy_function_delete_args(proc); + } + Assert(plrv != NULL); + } + + /* + * If it returns a set, call the iterator to get the next return item. + * We stay in the SPI context while doing this, because PyIter_Next() + * calls back into Python code which might contain SPI calls. + */ + if (proc->is_setof) + { + bool has_error = false; + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (proc->setof == NULL) + { + /* first time -- do checks and setup */ + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_ValuePerCall) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported set function return mode"), + errdetail("PL/Python set-returning functions only support returning only value per call."))); + } + rsi->returnMode = SFRM_ValuePerCall; + + /* Make iterator out of returned object */ + proc->setof = PyObject_GetIter(plrv); + Py_DECREF(plrv); + plrv = NULL; + + if (proc->setof == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("returned object cannot be iterated"), + errdetail("PL/Python set-returning functions must return an iterable object."))); + } + + /* Fetch next from iterator */ + plrv = PyIter_Next(proc->setof); + if (plrv) + rsi->isDone = ExprMultipleResult; + else + { + rsi->isDone = ExprEndResult; + has_error = PyErr_Occurred() != NULL; + } + + if (rsi->isDone == ExprEndResult) + { + /* Iterator is exhausted or error happened */ + Py_DECREF(proc->setof); + proc->setof = NULL; + + Py_XDECREF(plargs); + Py_XDECREF(plrv); + + PLy_function_delete_args(proc); + + if (has_error) + PLy_elog(ERROR, "error fetching next item from iterator"); + + /* Disconnect from the SPI manager before returning */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + fcinfo->isnull = true; + return (Datum) NULL; + } + } + + /* + * Disconnect from SPI manager and then create the return values datum + * (if the input function does a palloc for it this must not be + * allocated in the SPI memory context because SPI_finish would free + * it). + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + plerrcontext.callback = plpython_return_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * If the function is declared to return void, the Python return value + * must be None. For void-returning functions, we also treat a None + * return value as a special "void datum" rather than NULL (as is the + * case for non-void-returning functions). + */ + if (proc->result.out.d.typoid == VOIDOID) + { + if (plrv != Py_None) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function with return type \"void\" did not return None"))); + + fcinfo->isnull = false; + rv = (Datum) 0; + } + else if (plrv == Py_None) + { + fcinfo->isnull = true; + if (proc->result.is_rowtype < 1) + rv = InputFunctionCall(&proc->result.out.d.typfunc, + NULL, + proc->result.out.d.typioparam, + -1); + else + /* Tuple as None */ + rv = (Datum) NULL; + } + else if (proc->result.is_rowtype >= 1) + { + TupleDesc desc; + HeapTuple tuple = NULL; + + /* make sure it's not an unnamed record */ + Assert((proc->result.out.d.typoid == RECORDOID && + proc->result.out.d.typmod != -1) || + (proc->result.out.d.typoid != RECORDOID && + proc->result.out.d.typmod == -1)); + + desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, + proc->result.out.d.typmod); + + tuple = PLyObject_ToTuple(&proc->result, desc, plrv); + + if (tuple != NULL) + { + fcinfo->isnull = false; + rv = HeapTupleGetDatum(tuple); + } + else + { + fcinfo->isnull = true; + rv = (Datum) NULL; + } + } + else + { + fcinfo->isnull = false; + rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv); + } + } + PG_CATCH(); + { + Py_XDECREF(plargs); + Py_XDECREF(plrv); + + /* + * If there was an error the iterator might have not been exhausted + * yet. Set it to NULL so the next invocation of the function will + * start the iteration again. + */ + Py_XDECREF(proc->setof); + proc->setof = NULL; + + PG_RE_THROW(); + } + PG_END_TRY(); + + error_context_stack = plerrcontext.previous; + + Py_XDECREF(plargs); + Py_DECREF(plrv); + + return rv; +} + +/* trigger subhandler + * + * the python function is expected to return Py_None if the tuple is + * acceptable and unmodified. Otherwise it should return a PyString + * object who's value is SKIP, or MODIFY. SKIP means don't perform + * this action. MODIFY means the tuple has been modified, so update + * tuple and perform action. SKIP and MODIFY assume the trigger fires + * BEFORE the event and is ROW level. postgres expects the function + * to take no arguments and return an argument of type trigger. + */ +HeapTuple +PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + HeapTuple rv = NULL; + PyObject *volatile plargs = NULL; + PyObject *volatile plrv = NULL; + TriggerData *tdata; + + Assert(CALLED_AS_TRIGGER(fcinfo)); + + /* + * Input/output conversion for trigger tuples. Use the result TypeInfo + * variable to store the tuple conversion info. We do this over again on + * each call to cover the possibility that the relation's tupdesc changed + * since the trigger was last called. PLy_input_tuple_funcs and + * PLy_output_tuple_funcs are responsible for not doing repetitive work. + */ + tdata = (TriggerData *) fcinfo->context; + + PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); + PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); + + PG_TRY(); + { + plargs = PLy_trigger_build_args(fcinfo, proc, &rv); + plrv = PLy_procedure_call(proc, "TD", plargs); + + Assert(plrv != NULL); + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + /* + * return of None means we're happy with the tuple + */ + if (plrv != Py_None) + { + char *srv; + + if (PyString_Check(plrv)) + srv = PyString_AsString(plrv); + else if (PyUnicode_Check(plrv)) + srv = PLyUnicode_AsString(plrv); + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None or a string."))); + srv = NULL; /* keep compiler quiet */ + } + + if (pg_strcasecmp(srv, "SKIP") == 0) + rv = NULL; + else if (pg_strcasecmp(srv, "MODIFY") == 0) + { + TriggerData *tdata = (TriggerData *) fcinfo->context; + + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || + TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + rv = PLy_modify_tuple(proc, plargs, tdata, rv); + else + ereport(WARNING, + (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); + } + else if (pg_strcasecmp(srv, "OK") != 0) + { + /* + * accept "OK" as an alternative to None; otherwise, raise an + * error + */ + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); + } + } + } + PG_CATCH(); + { + Py_XDECREF(plargs); + Py_XDECREF(plrv); + + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(plargs); + Py_DECREF(plrv); + + return rv; +} + +/* helper functions for Python code execution */ + +static PyObject * +PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + PyObject *volatile arg = NULL; + PyObject *volatile args = NULL; + int i; + + PG_TRY(); + { + args = PyList_New(proc->nargs); + for (i = 0; i < proc->nargs; i++) + { + if (proc->args[i].is_rowtype > 0) + { + if (fcinfo->argnull[i]) + arg = NULL; + else + { + HeapTupleHeader td; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tmptup; + + td = DatumGetHeapTupleHeader(fcinfo->arg[i]); + /* Extract rowtype info and find a tupdesc */ + tupType = HeapTupleHeaderGetTypeId(td); + tupTypmod = HeapTupleHeaderGetTypMod(td); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* Set up I/O funcs if not done yet */ + if (proc->args[i].is_rowtype != 1) + PLy_input_tuple_funcs(&(proc->args[i]), tupdesc); + + /* Build a temporary HeapTuple control structure */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + + arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); + ReleaseTupleDesc(tupdesc); + } + } + else + { + if (fcinfo->argnull[i]) + arg = NULL; + else + { + arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d), + fcinfo->arg[i]); + } + } + + if (arg == NULL) + { + Py_INCREF(Py_None); + arg = Py_None; + } + + if (PyList_SetItem(args, i, arg) == -1) + PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments"); + + if (proc->argnames && proc->argnames[i] && + PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) + PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments"); + arg = NULL; + } + + /* Set up output conversion for functions returning RECORD */ + if (proc->result.out.d.typoid == RECORDOID) + { + TupleDesc desc; + + if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + /* cache the output conversion functions */ + PLy_output_record_funcs(&(proc->result), desc); + } + } + PG_CATCH(); + { + Py_XDECREF(arg); + Py_XDECREF(args); + + PG_RE_THROW(); + } + PG_END_TRY(); + + return args; +} + +static void +PLy_function_delete_args(PLyProcedure *proc) +{ + int i; + + if (!proc->argnames) + return; + + for (i = 0; i < proc->nargs; i++) + if (proc->argnames[i]) + PyDict_DelItemString(proc->globals, proc->argnames[i]); +} + +static void +plpython_return_error_callback(void *arg) +{ + if (PLy_curr_procedure) + errcontext("while creating return value"); +} + +static PyObject * +PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) +{ + TriggerData *tdata = (TriggerData *) fcinfo->context; + PyObject *pltname, + *pltevent, + *pltwhen, + *pltlevel, + *pltrelid, + *plttablename, + *plttableschema; + PyObject *pltargs, + *pytnew, + *pytold; + PyObject *volatile pltdata = NULL; + char *stroid; + + PG_TRY(); + { + pltdata = PyDict_New(); + if (!pltdata) + PLy_elog(ERROR, "could not create new dictionary while building trigger arguments"); + + pltname = PyString_FromString(tdata->tg_trigger->tgname); + PyDict_SetItemString(pltdata, "name", pltname); + Py_DECREF(pltname); + + stroid = DatumGetCString(DirectFunctionCall1(oidout, + ObjectIdGetDatum(tdata->tg_relation->rd_id))); + pltrelid = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "relid", pltrelid); + Py_DECREF(pltrelid); + pfree(stroid); + + stroid = SPI_getrelname(tdata->tg_relation); + plttablename = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "table_name", plttablename); + Py_DECREF(plttablename); + pfree(stroid); + + stroid = SPI_getnspname(tdata->tg_relation); + plttableschema = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "table_schema", plttableschema); + Py_DECREF(plttableschema); + pfree(stroid); + + if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) + pltwhen = PyString_FromString("BEFORE"); + else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) + pltwhen = PyString_FromString("AFTER"); + else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event)) + pltwhen = PyString_FromString("INSTEAD OF"); + else + { + elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); + pltwhen = NULL; /* keep compiler quiet */ + } + PyDict_SetItemString(pltdata, "when", pltwhen); + Py_DECREF(pltwhen); + + if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) + { + pltlevel = PyString_FromString("ROW"); + PyDict_SetItemString(pltdata, "level", pltlevel); + Py_DECREF(pltlevel); + + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) + { + pltevent = PyString_FromString("INSERT"); + + PyDict_SetItemString(pltdata, "old", Py_None); + pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, + tdata->tg_relation->rd_att); + PyDict_SetItemString(pltdata, "new", pytnew); + Py_DECREF(pytnew); + *rv = tdata->tg_trigtuple; + } + else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) + { + pltevent = PyString_FromString("DELETE"); + + PyDict_SetItemString(pltdata, "new", Py_None); + pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, + tdata->tg_relation->rd_att); + PyDict_SetItemString(pltdata, "old", pytold); + Py_DECREF(pytold); + *rv = tdata->tg_trigtuple; + } + else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + { + pltevent = PyString_FromString("UPDATE"); + + pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, + tdata->tg_relation->rd_att); + PyDict_SetItemString(pltdata, "new", pytnew); + Py_DECREF(pytnew); + pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, + tdata->tg_relation->rd_att); + PyDict_SetItemString(pltdata, "old", pytold); + Py_DECREF(pytold); + *rv = tdata->tg_newtuple; + } + else + { + elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); + pltevent = NULL; /* keep compiler quiet */ + } + + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + } + else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) + { + pltlevel = PyString_FromString("STATEMENT"); + PyDict_SetItemString(pltdata, "level", pltlevel); + Py_DECREF(pltlevel); + + PyDict_SetItemString(pltdata, "old", Py_None); + PyDict_SetItemString(pltdata, "new", Py_None); + *rv = NULL; + + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) + pltevent = PyString_FromString("INSERT"); + else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) + pltevent = PyString_FromString("DELETE"); + else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + pltevent = PyString_FromString("UPDATE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) + pltevent = PyString_FromString("TRUNCATE"); + else + { + elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); + pltevent = NULL; /* keep compiler quiet */ + } + + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + } + else + elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); + + if (tdata->tg_trigger->tgnargs) + { + /* + * all strings... + */ + int i; + PyObject *pltarg; + + pltargs = PyList_New(tdata->tg_trigger->tgnargs); + for (i = 0; i < tdata->tg_trigger->tgnargs; i++) + { + pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); + + /* + * stolen, don't Py_DECREF + */ + PyList_SetItem(pltargs, i, pltarg); + } + } + else + { + Py_INCREF(Py_None); + pltargs = Py_None; + } + PyDict_SetItemString(pltdata, "args", pltargs); + Py_DECREF(pltargs); + } + PG_CATCH(); + { + Py_XDECREF(pltdata); + PG_RE_THROW(); + } + PG_END_TRY(); + + return pltdata; +} + +static HeapTuple +PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, + HeapTuple otup) +{ + PyObject *volatile plntup; + PyObject *volatile plkeys; + PyObject *volatile platt; + PyObject *volatile plval; + PyObject *volatile plstr; + HeapTuple rtup; + int natts, + i, + attn, + atti; + int *volatile modattrs; + Datum *volatile modvalues; + char *volatile modnulls; + TupleDesc tupdesc; + ErrorContextCallback plerrcontext; + + plerrcontext.callback = plpython_trigger_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + plntup = plkeys = platt = plval = plstr = NULL; + modattrs = NULL; + modvalues = NULL; + modnulls = NULL; + + PG_TRY(); + { + if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) + ereport(ERROR, + (errmsg("TD[\"new\"] deleted, cannot modify row"))); + if (!PyDict_Check(plntup)) + ereport(ERROR, + (errmsg("TD[\"new\"] is not a dictionary"))); + Py_INCREF(plntup); + + plkeys = PyDict_Keys(plntup); + natts = PyList_Size(plkeys); + + modattrs = (int *) palloc(natts * sizeof(int)); + modvalues = (Datum *) palloc(natts * sizeof(Datum)); + modnulls = (char *) palloc(natts * sizeof(char)); + + tupdesc = tdata->tg_relation->rd_att; + + for (i = 0; i < natts; i++) + { + char *plattstr; + + platt = PyList_GetItem(plkeys, i); + if (PyString_Check(platt)) + plattstr = PyString_AsString(platt); + else if (PyUnicode_Check(platt)) + plattstr = PLyUnicode_AsString(platt); + else + { + ereport(ERROR, + (errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i))); + plattstr = NULL; /* keep compiler quiet */ + } + attn = SPI_fnumber(tupdesc, plattstr); + if (attn == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", + plattstr))); + atti = attn - 1; + + plval = PyDict_GetItem(plntup, platt); + if (plval == NULL) + elog(FATAL, "Python interpreter is probably corrupted"); + + Py_INCREF(plval); + + modattrs[i] = attn; + + if (tupdesc->attrs[atti]->attisdropped) + { + modvalues[i] = (Datum) 0; + modnulls[i] = 'n'; + } + else if (plval != Py_None) + { + PLyObToDatum *att = &proc->result.out.r.atts[atti]; + + modvalues[i] = (att->func) (att, + tupdesc->attrs[atti]->atttypmod, + plval); + modnulls[i] = ' '; + } + else + { + modvalues[i] = + InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, + NULL, + proc->result.out.r.atts[atti].typioparam, + tupdesc->attrs[atti]->atttypmod); + modnulls[i] = 'n'; + } + + Py_DECREF(plval); + plval = NULL; + } + + rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, + modattrs, modvalues, modnulls); + if (rtup == NULL) + elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result); + } + PG_CATCH(); + { + Py_XDECREF(plntup); + Py_XDECREF(plkeys); + Py_XDECREF(plval); + Py_XDECREF(plstr); + + if (modnulls) + pfree(modnulls); + if (modvalues) + pfree(modvalues); + if (modattrs) + pfree(modattrs); + + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(plntup); + Py_DECREF(plkeys); + + pfree(modattrs); + pfree(modvalues); + pfree(modnulls); + + error_context_stack = plerrcontext.previous; + + return rtup; +} + +static void +plpython_trigger_error_callback(void *arg) +{ + if (PLy_curr_procedure) + errcontext("while modifying trigger row"); +} + +/* execute Python code, propagate Python errors to the backend */ +static PyObject * +PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs) +{ + PyObject *rv; + int volatile save_subxact_level = list_length(explicit_subtransactions); + + PyDict_SetItemString(proc->globals, kargs, vargs); + + PG_TRY(); + { +#if PY_VERSION_HEX >= 0x03020000 + rv = PyEval_EvalCode(proc->code, + proc->globals, proc->globals); +#else + rv = PyEval_EvalCode((PyCodeObject *) proc->code, + proc->globals, proc->globals); +#endif + + /* + * Since plpy will only let you close subtransactions that you + * started, you cannot *unnest* subtransactions, only *nest* them + * without closing. + */ + Assert(list_length(explicit_subtransactions) >= save_subxact_level); + } + PG_CATCH(); + { + PLy_abort_open_subtransactions(save_subxact_level); + PG_RE_THROW(); + } + PG_END_TRY(); + + PLy_abort_open_subtransactions(save_subxact_level); + + /* If the Python code returned an error, propagate it */ + if (rv == NULL) + PLy_elog(ERROR, NULL); + + return rv; +} + +/* + * Abort lingering subtransactions that have been explicitly started + * by plpy.subtransaction().start() and not properly closed. + */ +static void +PLy_abort_open_subtransactions(int save_subxact_level) +{ + Assert(save_subxact_level >= 0); + + while (list_length(explicit_subtransactions) > save_subxact_level) + { + PLySubtransactionData *subtransactiondata; + + Assert(explicit_subtransactions != NIL); + + ereport(WARNING, + (errmsg("forcibly aborting a subtransaction that has not been exited"))); + + RollbackAndReleaseCurrentSubTransaction(); + + SPI_restore_connection(); + + subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions); + explicit_subtransactions = list_delete_first(explicit_subtransactions); + + MemoryContextSwitchTo(subtransactiondata->oldcontext); + CurrentResourceOwner = subtransactiondata->oldowner; + PLy_free(subtransactiondata); + } +} diff --git a/src/pl/plpython/plpy_exec.h b/src/pl/plpython/plpy_exec.h new file mode 100644 index 0000000000..86ceba13c4 --- /dev/null +++ b/src/pl/plpython/plpy_exec.h @@ -0,0 +1,13 @@ +/* + * src/pl/plpython/plpy_exec.h + */ + +#ifndef PLPY_EXEC_H +#define PLPY_EXEC_H + +#include "plpy_procedure.h" + +extern Datum PLy_exec_function(FunctionCallInfo, PLyProcedure *); +extern HeapTuple PLy_exec_trigger(FunctionCallInfo, PLyProcedure *); + +#endif /* PLPY_EXEC_H */ diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c new file mode 100644 index 0000000000..03c03d1d7d --- /dev/null +++ b/src/pl/plpython/plpy_main.c @@ -0,0 +1,325 @@ +/* + * PL/Python main entry points + * + * src/pl/plpython/plpy_main.c + */ + +#include "postgres.h" + +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "utils/guc.h" +#include "utils/syscache.h" + +#include "plpython.h" + +#include "plpy_main.h" + +#include "plpy_elog.h" +#include "plpy_exec.h" +#include "plpy_plpymodule.h" +#include "plpy_procedure.h" +#include "plpy_subxactobject.h" + + +/* + * exported functions + */ + +#if PY_MAJOR_VERSION >= 3 +/* Use separate names to avoid clash in pg_pltemplate */ +#define plpython_validator plpython3_validator +#define plpython_call_handler plpython3_call_handler +#define plpython_inline_handler plpython3_inline_handler +#endif + +extern void _PG_init(void); +extern Datum plpython_validator(PG_FUNCTION_ARGS); +extern Datum plpython_call_handler(PG_FUNCTION_ARGS); +extern Datum plpython_inline_handler(PG_FUNCTION_ARGS); + +#if PY_MAJOR_VERSION < 3 +/* Define aliases plpython2_call_handler etc */ +extern Datum plpython2_validator(PG_FUNCTION_ARGS); +extern Datum plpython2_call_handler(PG_FUNCTION_ARGS); +extern Datum plpython2_inline_handler(PG_FUNCTION_ARGS); +#endif + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(plpython_validator); +PG_FUNCTION_INFO_V1(plpython_call_handler); +PG_FUNCTION_INFO_V1(plpython_inline_handler); + +#if PY_MAJOR_VERSION < 3 +PG_FUNCTION_INFO_V1(plpython2_validator); +PG_FUNCTION_INFO_V1(plpython2_call_handler); +PG_FUNCTION_INFO_V1(plpython2_inline_handler); +#endif + + +static bool PLy_procedure_is_trigger(Form_pg_proc); +static void plpython_error_callback(void *); +static void plpython_inline_error_callback(void *); +static void PLy_init_interp(void); + +static const int plpython_python_version = PY_MAJOR_VERSION; + +/* initialize global variables */ +PyObject *PLy_interp_globals = NULL; + + +void +_PG_init(void) +{ + /* Be sure we do initialization only once (should be redundant now) */ + static bool inited = false; + const int **version_ptr; + + if (inited) + return; + + /* Be sure we don't run Python 2 and 3 in the same session (might crash) */ + version_ptr = (const int **) find_rendezvous_variable("plpython_python_version"); + if (!(*version_ptr)) + *version_ptr = &plpython_python_version; + else + { + if (**version_ptr != plpython_python_version) + ereport(FATAL, + (errmsg("Python major version mismatch in session"), + errdetail("This session has previously used Python major version %d, and it is now attempting to use Python major version %d.", + **version_ptr, plpython_python_version), + errhint("Start a new session to use a different Python major version."))); + } + + pg_bindtextdomain(TEXTDOMAIN); + +#if PY_MAJOR_VERSION >= 3 + PyImport_AppendInittab("plpy", PyInit_plpy); +#endif + Py_Initialize(); +#if PY_MAJOR_VERSION >= 3 + PyImport_ImportModule("plpy"); +#endif + PLy_init_interp(); + PLy_init_plpy(); + if (PyErr_Occurred()) + PLy_elog(FATAL, "untrapped error in initialization"); + + init_procedure_caches(); + + explicit_subtransactions = NIL; + + inited = true; +} + +/* + * This should only be called once from _PG_init. Initialize the Python + * interpreter and global data. + */ +void +PLy_init_interp(void) +{ + static PyObject *PLy_interp_safe_globals = NULL; + PyObject *mainmod; + + mainmod = PyImport_AddModule("__main__"); + if (mainmod == NULL || PyErr_Occurred()) + PLy_elog(ERROR, "could not import \"__main__\" module"); + Py_INCREF(mainmod); + PLy_interp_globals = PyModule_GetDict(mainmod); + PLy_interp_safe_globals = PyDict_New(); + PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals); + Py_DECREF(mainmod); + if (PLy_interp_globals == NULL || PyErr_Occurred()) + PLy_elog(ERROR, "could not initialize globals"); +} + +Datum +plpython_validator(PG_FUNCTION_ARGS) +{ + Oid funcoid = PG_GETARG_OID(0); + HeapTuple tuple; + Form_pg_proc procStruct; + bool is_trigger; + + if (!check_function_bodies) + { + PG_RETURN_VOID(); + } + + /* Get the new function's pg_proc entry */ + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", funcoid); + procStruct = (Form_pg_proc) GETSTRUCT(tuple); + + is_trigger = PLy_procedure_is_trigger(procStruct); + + ReleaseSysCache(tuple); + + PLy_procedure_get(funcoid, is_trigger); + + PG_RETURN_VOID(); +} + +#if PY_MAJOR_VERSION < 3 +Datum +plpython2_validator(PG_FUNCTION_ARGS) +{ + return plpython_validator(fcinfo); +} +#endif /* PY_MAJOR_VERSION < 3 */ + +Datum +plpython_call_handler(PG_FUNCTION_ARGS) +{ + Datum retval; + PLyProcedure *save_curr_proc; + ErrorContextCallback plerrcontext; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + save_curr_proc = PLy_curr_procedure; + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpython_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + PG_TRY(); + { + PLyProcedure *proc; + + if (CALLED_AS_TRIGGER(fcinfo)) + { + HeapTuple trv; + + proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); + PLy_curr_procedure = proc; + trv = PLy_exec_trigger(fcinfo, proc); + retval = PointerGetDatum(trv); + } + else + { + proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); + PLy_curr_procedure = proc; + retval = PLy_exec_function(fcinfo, proc); + } + } + PG_CATCH(); + { + PLy_curr_procedure = save_curr_proc; + PyErr_Clear(); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* Pop the error context stack */ + error_context_stack = plerrcontext.previous; + + PLy_curr_procedure = save_curr_proc; + + return retval; +} + +#if PY_MAJOR_VERSION < 3 +Datum +plpython2_call_handler(PG_FUNCTION_ARGS) +{ + return plpython_call_handler(fcinfo); +} +#endif /* PY_MAJOR_VERSION < 3 */ + +Datum +plpython_inline_handler(PG_FUNCTION_ARGS) +{ + InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); + FunctionCallInfoData fake_fcinfo; + FmgrInfo flinfo; + PLyProcedure *save_curr_proc; + PLyProcedure proc; + ErrorContextCallback plerrcontext; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + save_curr_proc = PLy_curr_procedure; + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpython_inline_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); + MemSet(&flinfo, 0, sizeof(flinfo)); + fake_fcinfo.flinfo = &flinfo; + flinfo.fn_oid = InvalidOid; + flinfo.fn_mcxt = CurrentMemoryContext; + + MemSet(&proc, 0, sizeof(PLyProcedure)); + proc.pyname = PLy_strdup("__plpython_inline_block"); + proc.result.out.d.typoid = VOIDOID; + + PG_TRY(); + { + PLy_procedure_compile(&proc, codeblock->source_text); + PLy_curr_procedure = &proc; + PLy_exec_function(&fake_fcinfo, &proc); + } + PG_CATCH(); + { + PLy_procedure_delete(&proc); + PLy_curr_procedure = save_curr_proc; + PyErr_Clear(); + PG_RE_THROW(); + } + PG_END_TRY(); + + PLy_procedure_delete(&proc); + + /* Pop the error context stack */ + error_context_stack = plerrcontext.previous; + + PLy_curr_procedure = save_curr_proc; + + PG_RETURN_VOID(); +} + +#if PY_MAJOR_VERSION < 3 +Datum +plpython2_inline_handler(PG_FUNCTION_ARGS) +{ + return plpython_inline_handler(fcinfo); +} +#endif /* PY_MAJOR_VERSION < 3 */ + +static bool PLy_procedure_is_trigger(Form_pg_proc procStruct) +{ + return (procStruct->prorettype == TRIGGEROID || + (procStruct->prorettype == OPAQUEOID && + procStruct->pronargs == 0)); +} + +static void +plpython_error_callback(void *arg) +{ + if (PLy_curr_procedure) + errcontext("PL/Python function \"%s\"", + PLy_procedure_name(PLy_curr_procedure)); +} + +static void +plpython_inline_error_callback(void *arg) +{ + errcontext("PL/Python anonymous code block"); +} diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h new file mode 100644 index 0000000000..a71aead176 --- /dev/null +++ b/src/pl/plpython/plpy_main.h @@ -0,0 +1,13 @@ +/* + * src/pl/plpython/plpy_main.h + */ + +#ifndef PLPY_MAIN_H +#define PLPY_MAIN_H + +#include "plpy_procedure.h" + +/* the interpreter's globals dict */ +extern PyObject *PLy_interp_globals; + +#endif /* PLPY_MAIN_H */ diff --git a/src/pl/plpython/plpy_planobject.c b/src/pl/plpython/plpy_planobject.c new file mode 100644 index 0000000000..01b40d1d81 --- /dev/null +++ b/src/pl/plpython/plpy_planobject.c @@ -0,0 +1,128 @@ +/* + * the PLyPlan class + * + * src/pl/plpython/plpy_planobject.c + */ + +#include "postgres.h" + +#include "plpython.h" + +#include "plpy_planobject.h" + +#include "plpy_elog.h" + + +static void PLy_plan_dealloc(PyObject *); +static PyObject *PLy_plan_status(PyObject *, PyObject *); + +static char PLy_plan_doc[] = { + "Store a PostgreSQL plan" +}; + +static PyMethodDef PLy_plan_methods[] = { + {"status", PLy_plan_status, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_PlanType = { + PyVarObject_HEAD_INIT(NULL, 0) + "PLyPlan", /* tp_name */ + sizeof(PLyPlanObject), /* tp_size */ + 0, /* tp_itemsize */ + + /* + * methods + */ + PLy_plan_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + PLy_plan_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PLy_plan_methods, /* tp_tpmethods */ +}; + +void +PLy_plan_init_type(void) +{ + if (PyType_Ready(&PLy_PlanType) < 0) + elog(ERROR, "could not initialize PLy_PlanType"); +} + +PyObject * +PLy_plan_new(void) +{ + PLyPlanObject *ob; + + if ((ob = PyObject_New(PLyPlanObject, &PLy_PlanType)) == NULL) + return NULL; + + ob->plan = NULL; + ob->nargs = 0; + ob->types = NULL; + ob->values = NULL; + ob->args = NULL; + + return (PyObject *) ob; +} + +bool +is_PLyPlanObject(PyObject *ob) +{ + return ob->ob_type == &PLy_PlanType; +} + +static void +PLy_plan_dealloc(PyObject *arg) +{ + PLyPlanObject *ob = (PLyPlanObject *) arg; + + if (ob->plan) + SPI_freeplan(ob->plan); + if (ob->types) + PLy_free(ob->types); + if (ob->values) + PLy_free(ob->values); + if (ob->args) + { + int i; + + for (i = 0; i < ob->nargs; i++) + PLy_typeinfo_dealloc(&ob->args[i]); + PLy_free(ob->args); + } + + arg->ob_type->tp_free(arg); +} + + +static PyObject * +PLy_plan_status(PyObject *self, PyObject *args) +{ + if (PyArg_ParseTuple(args, "")) + { + Py_INCREF(Py_True); + return Py_True; + /* return PyInt_FromLong(self->status); */ + } + PLy_exception_set(PLy_exc_error, "plan.status takes no arguments"); + return NULL; +} diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h new file mode 100644 index 0000000000..959813a24c --- /dev/null +++ b/src/pl/plpython/plpy_planobject.h @@ -0,0 +1,26 @@ +/* + * src/pl/plpython/plpy_planobject.h + */ + +#ifndef PLPY_PLANOBJECT_H +#define PLPY_PLANOBJECT_H + +#include "executor/spi.h" +#include "plpy_typeio.h" + + +typedef struct PLyPlanObject +{ + PyObject_HEAD + SPIPlanPtr plan; + int nargs; + Oid *types; + Datum *values; + PLyTypeInfo *args; +} PLyPlanObject; + +extern void PLy_plan_init_type(void); +extern PyObject *PLy_plan_new(void); +extern bool is_PLyPlanObject(PyObject *); + +#endif /* PLPY_PLANOBJECT_H */ diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c new file mode 100644 index 0000000000..e911107d9a --- /dev/null +++ b/src/pl/plpython/plpy_plpymodule.c @@ -0,0 +1,417 @@ +/* + * the plpy module + * + * src/pl/plpython/plpy_plpymodule.c + */ + +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "utils/builtins.h" + +#include "plpython.h" + +#include "plpy_plpymodule.h" + +#include "plpy_cursorobject.h" +#include "plpy_elog.h" +#include "plpy_planobject.h" +#include "plpy_resultobject.h" +#include "plpy_spi.h" +#include "plpy_subxactobject.h" + + +HTAB *PLy_spi_exceptions = NULL; + + +static void PLy_add_exceptions(PyObject *); +static void PLy_generate_spi_exceptions(PyObject *, PyObject *); + +/* module functions */ +static PyObject *PLy_debug(PyObject *, PyObject *); +static PyObject *PLy_log(PyObject *, PyObject *); +static PyObject *PLy_info(PyObject *, PyObject *); +static PyObject *PLy_notice(PyObject *, PyObject *); +static PyObject *PLy_warning(PyObject *, PyObject *); +static PyObject *PLy_error(PyObject *, PyObject *); +static PyObject *PLy_fatal(PyObject *, PyObject *); +static PyObject *PLy_quote_literal(PyObject *, PyObject *); +static PyObject *PLy_quote_nullable(PyObject *, PyObject *); +static PyObject *PLy_quote_ident(PyObject *, PyObject *); + + +/* A list of all known exceptions, generated from backend/utils/errcodes.txt */ +typedef struct ExceptionMap +{ + char *name; + char *classname; + int sqlstate; +} ExceptionMap; + +static const ExceptionMap exception_map[] = { +#include "spiexceptions.h" + {NULL, NULL, 0} +}; + +static PyMethodDef PLy_methods[] = { + /* + * logging methods + */ + {"debug", PLy_debug, METH_VARARGS, NULL}, + {"log", PLy_log, METH_VARARGS, NULL}, + {"info", PLy_info, METH_VARARGS, NULL}, + {"notice", PLy_notice, METH_VARARGS, NULL}, + {"warning", PLy_warning, METH_VARARGS, NULL}, + {"error", PLy_error, METH_VARARGS, NULL}, + {"fatal", PLy_fatal, METH_VARARGS, NULL}, + + /* + * create a stored plan + */ + {"prepare", PLy_spi_prepare, METH_VARARGS, NULL}, + + /* + * execute a plan or query + */ + {"execute", PLy_spi_execute, METH_VARARGS, NULL}, + + /* + * escaping strings + */ + {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL}, + {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL}, + {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL}, + + /* + * create the subtransaction context manager + */ + {"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL}, + + /* + * create a cursor + */ + {"cursor", PLy_cursor, METH_VARARGS, NULL}, + + {NULL, NULL, 0, NULL} +}; + +static PyMethodDef PLy_exc_methods[] = { + {NULL, NULL, 0, NULL} +}; + +#if PY_MAJOR_VERSION >= 3 +static PyModuleDef PLy_module = { + PyModuleDef_HEAD_INIT, /* m_base */ + "plpy", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + PLy_methods, /* m_methods */ +}; + +static PyModuleDef PLy_exc_module = { + PyModuleDef_HEAD_INIT, /* m_base */ + "spiexceptions", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + PLy_exc_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL /* m_free */ +}; + +/* + * Must have external linkage, because PyMODINIT_FUNC does dllexport on + * Windows-like platforms. + */ +PyMODINIT_FUNC +PyInit_plpy(void) +{ + PyObject *m; + + m = PyModule_Create(&PLy_module); + if (m == NULL) + return NULL; + + PLy_add_exceptions(m); + + return m; +} +#endif /* PY_MAJOR_VERSION >= 3 */ + +void +PLy_init_plpy(void) +{ + PyObject *main_mod, + *main_dict, + *plpy_mod; +#if PY_MAJOR_VERSION < 3 + PyObject *plpy; +#endif + + /* + * initialize plpy module + */ + PLy_plan_init_type(); + PLy_result_init_type(); + PLy_subtransaction_init_type(); + PLy_cursor_init_type(); + +#if PY_MAJOR_VERSION >= 3 + PyModule_Create(&PLy_module); + /* for Python 3 we initialized the exceptions in PyInit_plpy */ +#else + plpy = Py_InitModule("plpy", PLy_methods); + PLy_add_exceptions(plpy); +#endif + + /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */ + + /* + * initialize main module, and add plpy + */ + main_mod = PyImport_AddModule("__main__"); + main_dict = PyModule_GetDict(main_mod); + plpy_mod = PyImport_AddModule("plpy"); + PyDict_SetItemString(main_dict, "plpy", plpy_mod); + if (PyErr_Occurred()) + elog(ERROR, "could not initialize plpy"); +} + +static void +PLy_add_exceptions(PyObject *plpy) +{ + PyObject *excmod; + HASHCTL hash_ctl; + +#if PY_MAJOR_VERSION < 3 + excmod = Py_InitModule("spiexceptions", PLy_exc_methods); +#else + excmod = PyModule_Create(&PLy_exc_module); +#endif + if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0) + PLy_elog(ERROR, "could not add the spiexceptions module"); + + /* + * XXX it appears that in some circumstances the reference count of the + * spiexceptions module drops to zero causing a Python assert failure when + * the garbage collector visits the module. This has been observed on the + * buildfarm. To fix this, add an additional ref for the module here. + * + * This shouldn't cause a memory leak - we don't want this garbage + * collected, and this function shouldn't be called more than once per + * backend. + */ + Py_INCREF(excmod); + + PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); + PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); + PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL); + + Py_INCREF(PLy_exc_error); + PyModule_AddObject(plpy, "Error", PLy_exc_error); + Py_INCREF(PLy_exc_fatal); + PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal); + Py_INCREF(PLy_exc_spi_error); + PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error); + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(int); + hash_ctl.entrysize = sizeof(PLyExceptionEntry); + hash_ctl.hash = tag_hash; + PLy_spi_exceptions = hash_create("SPI exceptions", 256, + &hash_ctl, HASH_ELEM | HASH_FUNCTION); + + PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error); +} + +/* + * Add all the autogenerated exceptions as subclasses of SPIError + */ +static void +PLy_generate_spi_exceptions(PyObject *mod, PyObject *base) +{ + int i; + + for (i = 0; exception_map[i].name != NULL; i++) + { + bool found; + PyObject *exc; + PLyExceptionEntry *entry; + PyObject *sqlstate; + PyObject *dict = PyDict_New(); + + sqlstate = PyString_FromString(unpack_sql_state(exception_map[i].sqlstate)); + PyDict_SetItemString(dict, "sqlstate", sqlstate); + Py_DECREF(sqlstate); + exc = PyErr_NewException(exception_map[i].name, base, dict); + PyModule_AddObject(mod, exception_map[i].classname, exc); + entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate, + HASH_ENTER, &found); + entry->exc = exc; + Assert(!found); + } +} + + +/* + * the python interface to the elog function + * don't confuse these with PLy_elog + */ +static PyObject *PLy_output(volatile int, PyObject *, PyObject *); + +PyObject * +PLy_debug(PyObject *self, PyObject *args) +{ + return PLy_output(DEBUG2, self, args); +} + +PyObject * +PLy_log(PyObject *self, PyObject *args) +{ + return PLy_output(LOG, self, args); +} + +PyObject * +PLy_info(PyObject *self, PyObject *args) +{ + return PLy_output(INFO, self, args); +} + +PyObject * +PLy_notice(PyObject *self, PyObject *args) +{ + return PLy_output(NOTICE, self, args); +} + +PyObject * +PLy_warning(PyObject *self, PyObject *args) +{ + return PLy_output(WARNING, self, args); +} + +PyObject * +PLy_error(PyObject *self, PyObject *args) +{ + return PLy_output(ERROR, self, args); +} + +PyObject * +PLy_fatal(PyObject *self, PyObject *args) +{ + return PLy_output(FATAL, self, args); +} + +PyObject * +PLy_quote_literal(PyObject *self, PyObject *args) +{ + const char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + quoted = quote_literal_cstr(str); + ret = PyString_FromString(quoted); + pfree(quoted); + + return ret; +} + +PyObject * +PLy_quote_nullable(PyObject *self, PyObject *args) +{ + const char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "z", &str)) + return NULL; + + if (str == NULL) + return PyString_FromString("NULL"); + + quoted = quote_literal_cstr(str); + ret = PyString_FromString(quoted); + pfree(quoted); + + return ret; +} + +PyObject * +PLy_quote_ident(PyObject *self, PyObject *args) +{ + const char *str; + const char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + quoted = quote_identifier(str); + ret = PyString_FromString(quoted); + + return ret; +} + +static PyObject * +PLy_output(volatile int level, PyObject *self, PyObject *args) +{ + PyObject *volatile so; + char *volatile sv; + volatile MemoryContext oldcontext; + + if (PyTuple_Size(args) == 1) + { + /* + * Treat single argument specially to avoid undesirable ('tuple',) + * decoration. + */ + PyObject *o; + + PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o); + so = PyObject_Str(o); + } + else + so = PyObject_Str(args); + if (so == NULL || ((sv = PyString_AsString(so)) == NULL)) + { + level = ERROR; + sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog"); + } + + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + pg_verifymbstr(sv, strlen(sv), false); + elog(level, "%s", sv); + } + PG_CATCH(); + { + ErrorData *edata; + + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* + * Note: If sv came from PyString_AsString(), it points into storage + * owned by so. So free so after using sv. + */ + Py_XDECREF(so); + + /* Make Python raise the exception */ + PLy_exception_set(PLy_exc_error, "%s", edata->message); + return NULL; + } + PG_END_TRY(); + + Py_XDECREF(so); + + /* + * return a legal object so the interpreter will continue on its merry way + */ + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/pl/plpython/plpy_plpymodule.h b/src/pl/plpython/plpy_plpymodule.h new file mode 100644 index 0000000000..930ecfd1b1 --- /dev/null +++ b/src/pl/plpython/plpy_plpymodule.h @@ -0,0 +1,19 @@ +/* + * src/pl/plpython/plpy_plpymodule.h + */ + +#ifndef PLPY_PLPYMODULE_H +#define PLPY_PLPYMODULE_H + +#include "utils/hsearch.h" + +/* A hash table mapping sqlstates to exceptions, for speedy lookup */ +extern HTAB *PLy_spi_exceptions; + + +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC PyInit_plpy(void); +#endif +extern void PLy_init_plpy(void); + +#endif /* PLPY_PLPYMODULE_H */ diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c new file mode 100644 index 0000000000..b4f2abe262 --- /dev/null +++ b/src/pl/plpython/plpy_procedure.c @@ -0,0 +1,531 @@ +/* + * Python procedure manipulation for plpython + * + * src/pl/plpython/plpy_procedure.c + */ + +#include "postgres.h" + +#include "access/transam.h" +#include "funcapi.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/syscache.h" + +#include "plpython.h" + +#include "plpy_procedure.h" + +#include "plpy_elog.h" +#include "plpy_main.h" + + +PLyProcedure *PLy_curr_procedure = NULL; + + +static HTAB *PLy_procedure_cache = NULL; +static HTAB *PLy_trigger_cache = NULL; + +static PLyProcedure *PLy_procedure_create(HeapTuple, Oid, bool); +static bool PLy_procedure_argument_valid(PLyTypeInfo *); +static bool PLy_procedure_valid(PLyProcedure *, HeapTuple procTup); +static char *PLy_procedure_munge_source(const char *, const char *); + + +void +init_procedure_caches(void) +{ + HASHCTL hash_ctl; + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(PLyProcedureEntry); + hash_ctl.hash = oid_hash; + PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, + HASH_ELEM | HASH_FUNCTION); + + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(PLyProcedureEntry); + hash_ctl.hash = oid_hash; + PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl, + HASH_ELEM | HASH_FUNCTION); +} + +/* + * Get the name of the last procedure called by the backend (the + * innermost, if a plpython procedure call calls the backend and the + * backend calls another plpython procedure). + * + * NB: this returns the SQL name, not the internal Python procedure name + */ +char * +PLy_procedure_name(PLyProcedure *proc) +{ + if (proc == NULL) + return ""; + 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, "", Py_eval_input); + if (proc->code != NULL) + return; + } + + if (proc->proname) + PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", + proc->proname); + else + PLy_elog(ERROR, "could not compile anonymous PL/Python code block"); +} + +void +PLy_procedure_delete(PLyProcedure *proc) +{ + int i; + + Py_XDECREF(proc->code); + Py_XDECREF(proc->statics); + Py_XDECREF(proc->globals); + if (proc->proname) + PLy_free(proc->proname); + if (proc->pyname) + PLy_free(proc->pyname); + for (i = 0; i < proc->nargs; i++) + { + if (proc->args[i].is_rowtype == 1) + { + if (proc->args[i].in.r.atts) + PLy_free(proc->args[i].in.r.atts); + if (proc->args[i].out.r.atts) + PLy_free(proc->args[i].out.r.atts); + } + if (proc->argnames && proc->argnames[i]) + PLy_free(proc->argnames[i]); + } + if (proc->src) + PLy_free(proc->src); + if (proc->argnames) + PLy_free(proc->argnames); +} + +/* + * Check if our cached information about a datatype is still valid + */ +static bool +PLy_procedure_argument_valid(PLyTypeInfo *arg) +{ + HeapTuple relTup; + bool valid; + + /* Nothing to cache unless type is composite */ + if (arg->is_rowtype != 1) + return true; + + /* + * Zero typ_relid means that we got called on an output argument of a + * function returning a unnamed record type; the info for it can't change. + */ + if (!OidIsValid(arg->typ_relid)) + return true; + + /* Else we should have some cached data */ + Assert(TransactionIdIsValid(arg->typrel_xmin)); + Assert(ItemPointerIsValid(&arg->typrel_tid)); + + /* Get the pg_class tuple for the data type */ + relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); + + /* If it has changed, the cached data is not valid */ + valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) && + ItemPointerEquals(&arg->typrel_tid, &relTup->t_self)); + + ReleaseSysCache(relTup); + + return valid; +} + +/* + * Decide whether a cached PLyProcedure struct is still valid + */ +static bool +PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup) +{ + int i; + bool valid; + + Assert(proc != NULL); + + /* If the pg_proc tuple has changed, it's not valid */ + if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && + ItemPointerEquals(&proc->fn_tid, &procTup->t_self))) + return false; + + /* Else check the input argument datatypes */ + valid = true; + for (i = 0; i < proc->nargs; i++) + { + valid = PLy_procedure_argument_valid(&proc->args[i]); + + /* Short-circuit on first changed argument */ + if (!valid) + break; + } + + /* if the output type is composite, it might have changed */ + if (valid) + valid = PLy_procedure_argument_valid(&proc->result); + + return valid; +} + +static char * +PLy_procedure_munge_source(const char *name, const char *src) +{ + char *mrc, + *mp; + const char *sp; + size_t mlen, + plen; + + /* + * room for function source and the def statement + */ + mlen = (strlen(src) * 2) + strlen(name) + 16; + + mrc = palloc(mlen); + plen = snprintf(mrc, mlen, "def %s():\n\t", name); + Assert(plen >= 0 && plen < mlen); + + sp = src; + mp = mrc + plen; + + while (*sp != '\0') + { + if (*sp == '\r' && *(sp + 1) == '\n') + sp++; + + if (*sp == '\n' || *sp == '\r') + { + *mp++ = '\n'; + *mp++ = '\t'; + sp++; + } + else + *mp++ = *sp++; + } + *mp++ = '\n'; + *mp++ = '\n'; + *mp = '\0'; + + if (mp > (mrc + mlen)) + elog(FATAL, "buffer overrun in PLy_munge_source"); + + return mrc; +} diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h new file mode 100644 index 0000000000..632b975cc1 --- /dev/null +++ b/src/pl/plpython/plpy_procedure.h @@ -0,0 +1,52 @@ +/* + * src/pl/plpython/plpy_procedure.h + */ + +#ifndef PLPY_PROCEDURE_H +#define PLPY_PROCEDURE_H + +#include "plpy_typeio.h" + + +extern void init_procedure_caches(void); + + +/* cached procedure data */ +typedef struct PLyProcedure +{ + char *proname; /* SQL name of procedure */ + char *pyname; /* Python name of procedure */ + TransactionId fn_xmin; + ItemPointerData fn_tid; + bool fn_readonly; + PLyTypeInfo result; /* also used to store info for trigger tuple + * type */ + bool is_setof; /* true, if procedure returns result set */ + PyObject *setof; /* contents of result set. */ + char *src; /* textual procedure code, after mangling */ + char **argnames; /* Argument names */ + PLyTypeInfo args[FUNC_MAX_ARGS]; + int nargs; + PyObject *code; /* compiled procedure code */ + PyObject *statics; /* data saved across calls, local scope */ + PyObject *globals; /* data saved across calls, global scope */ +} PLyProcedure; + +/* the procedure cache entry */ +typedef struct PLyProcedureEntry +{ + Oid fn_oid; /* hash key */ + PLyProcedure *proc; +} PLyProcedureEntry; + +/* PLyProcedure manipulation */ +extern char *PLy_procedure_name(PLyProcedure *); +extern PLyProcedure *PLy_procedure_get(Oid, bool); +extern void PLy_procedure_compile(PLyProcedure *, const char *); +extern void PLy_procedure_delete(PLyProcedure *); + + +/* currently active plpython function */ +extern PLyProcedure *PLy_curr_procedure; + +#endif /* PLPY_PROCEDURE_H */ diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c new file mode 100644 index 0000000000..e1b89260ff --- /dev/null +++ b/src/pl/plpython/plpy_resultobject.c @@ -0,0 +1,180 @@ +/* + * the PLyResult class + * + * src/pl/plpython/plpy_resultobject.c + */ + +#include "postgres.h" + +#include "plpython.h" + +#include "plpy_resultobject.h" + + +static void PLy_result_dealloc(PyObject *); +static PyObject *PLy_result_nrows(PyObject *, PyObject *); +static PyObject *PLy_result_status(PyObject *, PyObject *); +static Py_ssize_t PLy_result_length(PyObject *); +static PyObject *PLy_result_item(PyObject *, Py_ssize_t); +static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t); +static int PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *); +static int PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); + +static char PLy_result_doc[] = { + "Results of a PostgreSQL query" +}; + +static PySequenceMethods PLy_result_as_sequence = { + PLy_result_length, /* sq_length */ + NULL, /* sq_concat */ + NULL, /* sq_repeat */ + PLy_result_item, /* sq_item */ + PLy_result_slice, /* sq_slice */ + PLy_result_ass_item, /* sq_ass_item */ + PLy_result_ass_slice, /* sq_ass_slice */ +}; + +static PyMethodDef PLy_result_methods[] = { + {"nrows", PLy_result_nrows, METH_VARARGS, NULL}, + {"status", PLy_result_status, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_ResultType = { + PyVarObject_HEAD_INIT(NULL, 0) + "PLyResult", /* tp_name */ + sizeof(PLyResultObject), /* tp_size */ + 0, /* tp_itemsize */ + + /* + * methods + */ + PLy_result_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + &PLy_result_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + PLy_result_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PLy_result_methods, /* tp_tpmethods */ +}; + +void +PLy_result_init_type(void) +{ + if (PyType_Ready(&PLy_ResultType) < 0) + elog(ERROR, "could not initialize PLy_ResultType"); +} + +PyObject * +PLy_result_new(void) +{ + PLyResultObject *ob; + + if ((ob = PyObject_New(PLyResultObject, &PLy_ResultType)) == NULL) + return NULL; + + /* ob->tuples = NULL; */ + + Py_INCREF(Py_None); + ob->status = Py_None; + ob->nrows = PyInt_FromLong(-1); + ob->rows = PyList_New(0); + + return (PyObject *) ob; +} + +static void +PLy_result_dealloc(PyObject *arg) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + Py_XDECREF(ob->nrows); + Py_XDECREF(ob->rows); + Py_XDECREF(ob->status); + + arg->ob_type->tp_free(arg); +} + +static PyObject * +PLy_result_nrows(PyObject *self, PyObject *args) +{ + PLyResultObject *ob = (PLyResultObject *) self; + + Py_INCREF(ob->nrows); + return ob->nrows; +} + +static PyObject * +PLy_result_status(PyObject *self, PyObject *args) +{ + PLyResultObject *ob = (PLyResultObject *) self; + + Py_INCREF(ob->status); + return ob->status; +} + +static Py_ssize_t +PLy_result_length(PyObject *arg) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyList_Size(ob->rows); +} + +static PyObject * +PLy_result_item(PyObject *arg, Py_ssize_t idx) +{ + PyObject *rv; + PLyResultObject *ob = (PLyResultObject *) arg; + + rv = PyList_GetItem(ob->rows, idx); + if (rv != NULL) + Py_INCREF(rv); + return rv; +} + +static int +PLy_result_ass_item(PyObject *arg, Py_ssize_t idx, PyObject *item) +{ + int rv; + PLyResultObject *ob = (PLyResultObject *) arg; + + Py_INCREF(item); + rv = PyList_SetItem(ob->rows, idx, item); + return rv; +} + +static PyObject * +PLy_result_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyList_GetSlice(ob->rows, lidx, hidx); +} + +static int +PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice) +{ + int rv; + PLyResultObject *ob = (PLyResultObject *) arg; + + rv = PyList_SetSlice(ob->rows, lidx, hidx, slice); + return rv; +} diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h new file mode 100644 index 0000000000..719828a3ef --- /dev/null +++ b/src/pl/plpython/plpy_resultobject.h @@ -0,0 +1,20 @@ +/* + * src/pl/plpython/plpy_resultobject.h + */ + +#ifndef PLPY_RESULTOBJECT_H +#define PLPY_RESULTOBJECT_H + +typedef struct PLyResultObject +{ + PyObject_HEAD + /* HeapTuple *tuples; */ + PyObject *nrows; /* number of rows returned by query */ + PyObject *rows; /* data rows, or None if no data returned */ + PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */ +} PLyResultObject; + +extern void PLy_result_init_type(void); +extern PyObject *PLy_result_new(void); + +#endif /* PLPY_RESULTOBJECT_H */ diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c new file mode 100644 index 0000000000..5e3099ee5b --- /dev/null +++ b/src/pl/plpython/plpy_spi.c @@ -0,0 +1,559 @@ +/* + * interface to SPI functions + * + * src/pl/plpython/plpy_spi.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "executor/spi_priv.h" +#include "mb/pg_wchar.h" +#include "parser/parse_type.h" +#include "utils/syscache.h" + +#include "plpython.h" + +#include "plpy_spi.h" + +#include "plpy_elog.h" +#include "plpy_planobject.h" +#include "plpy_plpymodule.h" +#include "plpy_procedure.h" +#include "plpy_resultobject.h" + + +static PyObject *PLy_spi_execute_query(char *, long ); +static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long); +static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int); +static void PLy_spi_exception_set(PyObject *, ErrorData *); + + +/* prepare(query="select * from foo") + * prepare(query="select * from foo where bar = $1", params=["text"]) + * prepare(query="select * from foo where bar = $1", params=["text"], limit=5) + */ +PyObject * +PLy_spi_prepare(PyObject *self, PyObject *args) +{ + PLyPlanObject *plan; + PyObject *list = NULL; + PyObject *volatile optr = NULL; + char *query; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + volatile int nargs; + + if (!PyArg_ParseTuple(args, "s|O", &query, &list)) + return NULL; + + if (list && (!PySequence_Check(list))) + { + PLy_exception_set(PyExc_TypeError, + "second argument of plpy.prepare must be a sequence"); + return NULL; + } + + if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) + return NULL; + + nargs = list ? PySequence_Length(list) : 0; + + plan->nargs = nargs; + plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL; + plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL; + plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL; + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + int i; + + /* + * the other loop might throw an exception, if PLyTypeInfo member + * isn't properly initialized the Py_DECREF(plan) will go boom + */ + for (i = 0; i < nargs; i++) + { + PLy_typeinfo_init(&plan->args[i]); + plan->values[i] = PointerGetDatum(NULL); + } + + for (i = 0; i < nargs; i++) + { + char *sptr; + HeapTuple typeTup; + Oid typeId; + int32 typmod; + Form_pg_type typeStruct; + + optr = PySequence_GetItem(list, i); + if (PyString_Check(optr)) + sptr = PyString_AsString(optr); + else if (PyUnicode_Check(optr)) + sptr = PLyUnicode_AsString(optr); + else + { + ereport(ERROR, + (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); + sptr = NULL; /* keep compiler quiet */ + } + + /******************************************************** + * Resolve argument type names and then look them up by + * oid in the system cache, and remember the required + *information for input conversion. + ********************************************************/ + + parseTypeString(sptr, &typeId, &typmod); + + typeTup = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(typeId)); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", typeId); + + Py_DECREF(optr); + + /* + * set optr to NULL, so we won't try to unref it again in case of + * an error + */ + optr = NULL; + + plan->types[i] = typeId; + typeStruct = (Form_pg_type) GETSTRUCT(typeTup); + if (typeStruct->typtype != TYPTYPE_COMPOSITE) + PLy_output_datum_func(&plan->args[i], typeTup); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("plpy.prepare does not support composite types"))); + ReleaseSysCache(typeTup); + } + + pg_verifymbstr(query, strlen(query), false); + plan->plan = SPI_prepare(query, plan->nargs, plan->types); + if (plan->plan == NULL) + elog(ERROR, "SPI_prepare failed: %s", + SPI_result_code_string(SPI_result)); + + /* transfer plan from procCxt to topCxt */ + if (SPI_keepplan(plan->plan)) + elog(ERROR, "SPI_keepplan failed"); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + Py_DECREF(plan); + Py_XDECREF(optr); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + Assert(plan->plan != NULL); + return (PyObject *) plan; +} + +/* execute(query="select * from foo", limit=5) + * execute(plan=plan, values=(foo, bar), limit=5) + */ +PyObject * +PLy_spi_execute(PyObject *self, PyObject *args) +{ + char *query; + PyObject *plan; + PyObject *list = NULL; + long limit = 0; + + if (PyArg_ParseTuple(args, "s|l", &query, &limit)) + return PLy_spi_execute_query(query, limit); + + PyErr_Clear(); + + if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) && + is_PLyPlanObject(plan)) + return PLy_spi_execute_plan(plan, list, limit); + + PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan"); + return NULL; +} + +static PyObject * +PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) +{ + volatile int nargs; + int i, + rv; + PLyPlanObject *plan; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + PyObject *ret; + + if (list != NULL) + { + if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list)) + { + PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument"); + return NULL; + } + nargs = PySequence_Length(list); + } + else + nargs = 0; + + plan = (PLyPlanObject *) ob; + + if (nargs != plan->nargs) + { + char *sv; + PyObject *so = PyObject_Str(list); + + if (!so) + PLy_elog(ERROR, "could not execute plan"); + sv = PyString_AsString(so); + PLy_exception_set_plural(PyExc_TypeError, + "Expected sequence of %d argument, got %d: %s", + "Expected sequence of %d arguments, got %d: %s", + plan->nargs, + plan->nargs, nargs, sv); + Py_DECREF(so); + + return NULL; + } + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + char *volatile nulls; + volatile int j; + + if (nargs > 0) + nulls = palloc(nargs * sizeof(char)); + else + nulls = NULL; + + for (j = 0; j < nargs; j++) + { + PyObject *elem; + + elem = PySequence_GetItem(list, j); + if (elem != Py_None) + { + PG_TRY(); + { + plan->values[j] = + plan->args[j].out.d.func(&(plan->args[j].out.d), + -1, + elem); + } + PG_CATCH(); + { + Py_DECREF(elem); + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(elem); + nulls[j] = ' '; + } + else + { + Py_DECREF(elem); + plan->values[j] = + InputFunctionCall(&(plan->args[j].out.d.typfunc), + NULL, + plan->args[j].out.d.typioparam, + -1); + nulls[j] = 'n'; + } + } + + rv = SPI_execute_plan(plan->plan, plan->values, nulls, + PLy_curr_procedure->fn_readonly, limit); + ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); + + if (nargs > 0) + pfree(nulls); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + int k; + + /* + * cleanup plan->values array + */ + for (k = 0; k < nargs; k++) + { + if (!plan->args[k].out.d.typbyval && + (plan->values[k] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); + } + } + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + for (i = 0; i < nargs; i++) + { + if (!plan->args[i].out.d.typbyval && + (plan->values[i] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[i])); + plan->values[i] = PointerGetDatum(NULL); + } + } + + if (rv < 0) + { + PLy_exception_set(PLy_exc_spi_error, + "SPI_execute_plan failed: %s", + SPI_result_code_string(rv)); + return NULL; + } + + return ret; +} + +static PyObject * +PLy_spi_execute_query(char *query, long limit) +{ + int rv; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + PyObject *ret; + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + pg_verifymbstr(query, strlen(query), false); + rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit); + ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + if (rv < 0) + { + PLy_exception_set(PLy_exc_spi_error, + "SPI_execute failed: %s", + SPI_result_code_string(rv)); + return NULL; + } + + return ret; +} + +static PyObject * +PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) +{ + PLyResultObject *result; + volatile MemoryContext oldcontext; + + result = (PLyResultObject *) PLy_result_new(); + Py_DECREF(result->status); + result->status = PyInt_FromLong(status); + + if (status > 0 && tuptable == NULL) + { + Py_DECREF(result->nrows); + result->nrows = PyInt_FromLong(rows); + } + else if (status > 0 && tuptable != NULL) + { + PLyTypeInfo args; + int i; + + Py_DECREF(result->nrows); + result->nrows = PyInt_FromLong(rows); + PLy_typeinfo_init(&args); + + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + if (rows) + { + Py_DECREF(result->rows); + result->rows = PyList_New(rows); + + PLy_input_tuple_funcs(&args, tuptable->tupdesc); + for (i = 0; i < rows; i++) + { + PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], + tuptable->tupdesc); + + PyList_SetItem(result->rows, i, row); + } + } + } + PG_CATCH(); + { + MemoryContextSwitchTo(oldcontext); + if (!PyErr_Occurred()) + PLy_exception_set(PLy_exc_error, + "unrecognized error in PLy_spi_execute_fetch_result"); + PLy_typeinfo_dealloc(&args); + SPI_freetuptable(tuptable); + Py_DECREF(result); + return NULL; + } + PG_END_TRY(); + + PLy_typeinfo_dealloc(&args); + SPI_freetuptable(tuptable); + } + + return (PyObject *) result; +} + +/* + * Utilities for running SPI functions in subtransactions. + * + * Usage: + * + * MemoryContext oldcontext = CurrentMemoryContext; + * ResourceOwner oldowner = CurrentResourceOwner; + * + * PLy_spi_subtransaction_begin(oldcontext, oldowner); + * PG_TRY(); + * { + * + * PLy_spi_subtransaction_commit(oldcontext, oldowner); + * } + * PG_CATCH(); + * { + * + * PLy_spi_subtransaction_abort(oldcontext, oldowner); + * return NULL; + * } + * PG_END_TRY(); + * + * These utilities take care of restoring connection to the SPI manager and + * setting a Python exception in case of an abort. + */ +void +PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner) +{ + BeginInternalSubTransaction(NULL); + /* Want to run inside function's memory context */ + MemoryContextSwitchTo(oldcontext); +} + +void +PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner) +{ + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + /* + * AtEOSubXact_SPI() should not have popped any SPI context, but just + * in case it did, make sure we remain connected. + */ + SPI_restore_connection(); +} + +void +PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner) +{ + ErrorData *edata; + PLyExceptionEntry *entry; + PyObject *exc; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + /* + * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will have + * left us in a disconnected state. We need this hack to return to + * connected state. + */ + SPI_restore_connection(); + + /* Look up the correct exception */ + entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode), + HASH_FIND, NULL); + /* We really should find it, but just in case have a fallback */ + Assert(entry != NULL); + exc = entry ? entry->exc : PLy_exc_spi_error; + /* Make Python raise the exception */ + PLy_spi_exception_set(exc, edata); + FreeErrorData(edata); +} + +/* + * Raise a SPIError, passing in it more error details, like the + * internal query and error position. + */ +static void +PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) +{ + PyObject *args = NULL; + PyObject *spierror = NULL; + PyObject *spidata = NULL; + + args = Py_BuildValue("(s)", edata->message); + if (!args) + goto failure; + + /* create a new SPI exception with the error message as the parameter */ + spierror = PyObject_CallObject(excclass, args); + if (!spierror) + goto failure; + + spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, + edata->internalquery, edata->internalpos); + if (!spidata) + goto failure; + + if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) + goto failure; + + PyErr_SetObject(excclass, spierror); + + Py_DECREF(args); + Py_DECREF(spierror); + Py_DECREF(spidata); + return; + +failure: + Py_XDECREF(args); + Py_XDECREF(spierror); + Py_XDECREF(spidata); + elog(ERROR, "could not convert SPI error to Python exception"); +} diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h new file mode 100644 index 0000000000..c59482a3f0 --- /dev/null +++ b/src/pl/plpython/plpy_spi.h @@ -0,0 +1,25 @@ +/* + * src/pl/plpython/plpy_spi.h + */ + +#ifndef PLPY_SPI_H +#define PLPY_SPI_H + +#include "utils/palloc.h" +#include "utils/resowner.h" + +extern PyObject *PLy_spi_prepare(PyObject *, PyObject *); +extern PyObject *PLy_spi_execute(PyObject *, PyObject *); + +typedef struct PLyExceptionEntry +{ + int sqlstate; /* hash key, must be first */ + PyObject *exc; /* corresponding exception */ +} PLyExceptionEntry; + +/* handling of SPI operations inside subtransactions */ +extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner); +extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); +extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); + +#endif /* PLPY_SPI_H */ diff --git a/src/pl/plpython/plpy_subxactobject.c b/src/pl/plpython/plpy_subxactobject.c new file mode 100644 index 0000000000..6c3cc69adf --- /dev/null +++ b/src/pl/plpython/plpy_subxactobject.c @@ -0,0 +1,217 @@ +/* + * the PLySubtransaction class + * + * src/pl/plpython/plpy_subxactobject.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "executor/spi.h" + +#include "plpython.h" + +#include "plpy_subxactobject.h" + +#include "plpy_elog.h" + + +List *explicit_subtransactions = NIL; + + +static void PLy_subtransaction_dealloc(PyObject *); +static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *); +static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *); + +static char PLy_subtransaction_doc[] = { + "PostgreSQL subtransaction context manager" +}; + +static PyMethodDef PLy_subtransaction_methods[] = { + {"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL}, + {"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL}, + /* user-friendly names for Python <2.6 */ + {"enter", PLy_subtransaction_enter, METH_VARARGS, NULL}, + {"exit", PLy_subtransaction_exit, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_SubtransactionType = { + PyVarObject_HEAD_INIT(NULL, 0) + "PLySubtransaction", /* tp_name */ + sizeof(PLySubtransactionObject), /* tp_size */ + 0, /* tp_itemsize */ + + /* + * methods + */ + PLy_subtransaction_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + PLy_subtransaction_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PLy_subtransaction_methods, /* tp_tpmethods */ +}; + + +void +PLy_subtransaction_init_type(void) +{ + if (PyType_Ready(&PLy_SubtransactionType) < 0) + elog(ERROR, "could not initialize PLy_SubtransactionType"); +} + +/* s = plpy.subtransaction() */ +PyObject * +PLy_subtransaction_new(PyObject *self, PyObject *unused) +{ + PLySubtransactionObject *ob; + + ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType); + + if (ob == NULL) + return NULL; + + ob->started = false; + ob->exited = false; + + return (PyObject *) ob; +} + +/* Python requires a dealloc function to be defined */ +static void +PLy_subtransaction_dealloc(PyObject *subxact) +{ +} + +/* + * subxact.__enter__() or subxact.enter() + * + * Start an explicit subtransaction. SPI calls within an explicit + * subtransaction will not start another one, so you can atomically + * execute many SPI calls and still get a controllable exception if + * one of them fails. + */ +static PyObject * +PLy_subtransaction_enter(PyObject *self, PyObject *unused) +{ + PLySubtransactionData *subxactdata; + MemoryContext oldcontext; + PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; + + if (subxact->started) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered"); + return NULL; + } + + if (subxact->exited) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); + return NULL; + } + + subxact->started = true; + oldcontext = CurrentMemoryContext; + + subxactdata = PLy_malloc(sizeof(*subxactdata)); + subxactdata->oldcontext = oldcontext; + subxactdata->oldowner = CurrentResourceOwner; + + BeginInternalSubTransaction(NULL); + /* Do not want to leave the previous memory context */ + MemoryContextSwitchTo(oldcontext); + + explicit_subtransactions = lcons(subxactdata, explicit_subtransactions); + + Py_INCREF(self); + return self; +} + +/* + * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb) + * + * Exit an explicit subtransaction. exc_type is an exception type, exc + * is the exception object, tb is the traceback. If exc_type is None, + * commit the subtransactiony, if not abort it. + * + * The method signature is chosen to allow subtransaction objects to + * be used as context managers as described in + * . + */ +static PyObject * +PLy_subtransaction_exit(PyObject *self, PyObject *args) +{ + PyObject *type; + PyObject *value; + PyObject *traceback; + PLySubtransactionData *subxactdata; + PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; + + if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback)) + return NULL; + + if (!subxact->started) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered"); + return NULL; + } + + if (subxact->exited) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); + return NULL; + } + + if (explicit_subtransactions == NIL) + { + PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from"); + return NULL; + } + + subxact->exited = true; + + if (type != Py_None) + { + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + } + else + { + ReleaseCurrentSubTransaction(); + } + + subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions); + explicit_subtransactions = list_delete_first(explicit_subtransactions); + + MemoryContextSwitchTo(subxactdata->oldcontext); + CurrentResourceOwner = subxactdata->oldowner; + PLy_free(subxactdata); + + /* + * AtEOSubXact_SPI() should not have popped any SPI context, but just in + * case it did, make sure we remain connected. + */ + SPI_restore_connection(); + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/pl/plpython/plpy_subxactobject.h b/src/pl/plpython/plpy_subxactobject.h new file mode 100644 index 0000000000..0db8aa9f3f --- /dev/null +++ b/src/pl/plpython/plpy_subxactobject.h @@ -0,0 +1,29 @@ +/* + * src/pl/plpython/plpy_subxactobject.h + */ + +#ifndef PLPY_SUBXACTOBJECT +#define PLPY_SUBXACTOBJECT + +/* a list of nested explicit subtransactions */ +extern List *explicit_subtransactions; + + +typedef struct PLySubtransactionObject +{ + PyObject_HEAD + bool started; + bool exited; +} PLySubtransactionObject; + +/* explicit subtransaction data */ +typedef struct PLySubtransactionData +{ + MemoryContext oldcontext; + ResourceOwner oldowner; +} PLySubtransactionData; + +extern void PLy_subtransaction_init_type(void); +extern PyObject *PLy_subtransaction_new(PyObject *, PyObject *); + +#endif /* PLPY_SUBXACTOBJECT */ diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c new file mode 100644 index 0000000000..cd6a46d8da --- /dev/null +++ b/src/pl/plpython/plpy_typeio.c @@ -0,0 +1,1038 @@ +/* + * transforming Datums to Python objects and vice versa + * + * src/pl/plpython/plpy_typeio.c + */ + +#include "postgres.h" + +#include "access/transam.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "parser/parse_type.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +#include "plpython.h" + +#include "plpy_typeio.h" + +#include "plpy_elog.h" + + +/* I/O function caching */ +static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple); +static void PLy_output_datum_func2(PLyObToDatum *, HeapTuple); + +/* conversion from Datums to Python objects */ +static PyObject *PLyBool_FromBool(PLyDatumToOb *, Datum); +static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *, Datum); +static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *, Datum); +static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *, Datum); +static PyObject *PLyInt_FromInt16(PLyDatumToOb *, Datum); +static PyObject *PLyInt_FromInt32(PLyDatumToOb *, Datum); +static PyObject *PLyLong_FromInt64(PLyDatumToOb *, Datum); +static PyObject *PLyBytes_FromBytea(PLyDatumToOb *, Datum); +static PyObject *PLyString_FromDatum(PLyDatumToOb *, Datum); +static PyObject *PLyList_FromArray(PLyDatumToOb *, Datum); + +/* conversion from Python objects to Datums */ +static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *); +static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *); +static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *); +static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *); +static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *); + +/* conversion from Python objects to heap tuples (used by triggers and SRFs) */ +static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); +static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); +static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); + +/* make allocations in the TopMemoryContext */ +static void perm_fmgr_info(Oid, FmgrInfo *); + +void +PLy_typeinfo_init(PLyTypeInfo *arg) +{ + arg->is_rowtype = -1; + arg->in.r.natts = arg->out.r.natts = 0; + arg->in.r.atts = NULL; + arg->out.r.atts = NULL; + arg->typ_relid = InvalidOid; + arg->typrel_xmin = InvalidTransactionId; + ItemPointerSetInvalid(&arg->typrel_tid); +} + +void +PLy_typeinfo_dealloc(PLyTypeInfo *arg) +{ + if (arg->is_rowtype == 1) + { + if (arg->in.r.atts) + PLy_free(arg->in.r.atts); + if (arg->out.r.atts) + PLy_free(arg->out.r.atts); + } +} + +/* + * Conversion functions. Remember output from Python is input to + * PostgreSQL, and vice versa. + */ +void +PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup) +{ + if (arg->is_rowtype > 0) + elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); + arg->is_rowtype = 0; + PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); +} + +void +PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup) +{ + if (arg->is_rowtype > 0) + elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); + arg->is_rowtype = 0; + PLy_output_datum_func2(&(arg->out.d), typeTup); +} + +void +PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) +{ + int i; + + if (arg->is_rowtype == 0) + elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); + arg->is_rowtype = 1; + + if (arg->in.r.natts != desc->natts) + { + if (arg->in.r.atts) + PLy_free(arg->in.r.atts); + arg->in.r.natts = desc->natts; + arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); + } + + /* Can this be an unnamed tuple? If not, then an Assert would be enough */ + if (desc->tdtypmod != -1) + elog(ERROR, "received unnamed record type as input"); + + Assert(OidIsValid(desc->tdtypeid)); + + /* + * RECORDOID means we got called to create input functions for a tuple + * fetched by plpy.execute or for an anonymous record type + */ + if (desc->tdtypeid != RECORDOID) + { + HeapTuple relTup; + + /* Get the pg_class tuple corresponding to the type of the input */ + arg->typ_relid = typeidTypeRelid(desc->tdtypeid); + relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); + + /* Remember XMIN and TID for later validation if cache is still OK */ + arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data); + arg->typrel_tid = relTup->t_self; + + ReleaseSysCache(relTup); + } + + for (i = 0; i < desc->natts; i++) + { + HeapTuple typeTup; + + if (desc->attrs[i]->attisdropped) + continue; + + if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid) + continue; /* already set up this entry */ + + typeTup = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(desc->attrs[i]->atttypid)); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", + desc->attrs[i]->atttypid); + + PLy_input_datum_func2(&(arg->in.r.atts[i]), + desc->attrs[i]->atttypid, + typeTup); + + ReleaseSysCache(typeTup); + } +} + +void +PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) +{ + int i; + + if (arg->is_rowtype == 0) + elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); + arg->is_rowtype = 1; + + if (arg->out.r.natts != desc->natts) + { + if (arg->out.r.atts) + PLy_free(arg->out.r.atts); + arg->out.r.natts = desc->natts; + arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); + } + + Assert(OidIsValid(desc->tdtypeid)); + + /* + * RECORDOID means we got called to create output functions for an + * anonymous record type + */ + if (desc->tdtypeid != RECORDOID) + { + HeapTuple relTup; + + /* Get the pg_class tuple corresponding to the type of the output */ + arg->typ_relid = typeidTypeRelid(desc->tdtypeid); + relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); + + /* Remember XMIN and TID for later validation if cache is still OK */ + arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data); + arg->typrel_tid = relTup->t_self; + + ReleaseSysCache(relTup); + } + + for (i = 0; i < desc->natts; i++) + { + HeapTuple typeTup; + + if (desc->attrs[i]->attisdropped) + continue; + + if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid) + continue; /* already set up this entry */ + + typeTup = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(desc->attrs[i]->atttypid)); + if (!HeapTupleIsValid(typeTup)) + elog(ERROR, "cache lookup failed for type %u", + desc->attrs[i]->atttypid); + + PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); + + ReleaseSysCache(typeTup); + } +} + +void +PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc) +{ + /* + * If the output record functions are already set, we just have to check + * if the record descriptor has not changed + */ + if ((arg->is_rowtype == 1) && + (arg->out.d.typmod != -1) && + (arg->out.d.typmod == desc->tdtypmod)) + return; + + /* bless the record to make it known to the typcache lookup code */ + BlessTupleDesc(desc); + /* save the freshly generated typmod */ + arg->out.d.typmod = desc->tdtypmod; + /* proceed with normal I/O function caching */ + PLy_output_tuple_funcs(arg, desc); + + /* + * it should change is_rowtype to 1, so we won't go through this again + * unless the the output record description changes + */ + Assert(arg->is_rowtype == 1); +} + +PyObject * +PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc) +{ + PyObject *volatile dict; + int i; + + if (info->is_rowtype != 1) + elog(ERROR, "PLyTypeInfo structure describes a datum"); + + dict = PyDict_New(); + if (dict == NULL) + PLy_elog(ERROR, "could not create new dictionary"); + + PG_TRY(); + { + for (i = 0; i < info->in.r.natts; i++) + { + char *key; + Datum vattr; + bool is_null; + PyObject *value; + + if (desc->attrs[i]->attisdropped) + continue; + + key = NameStr(desc->attrs[i]->attname); + vattr = heap_getattr(tuple, (i + 1), desc, &is_null); + + if (is_null || info->in.r.atts[i].func == NULL) + PyDict_SetItemString(dict, key, Py_None); + else + { + value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr); + PyDict_SetItemString(dict, key, value); + Py_DECREF(value); + } + } + } + PG_CATCH(); + { + Py_DECREF(dict); + PG_RE_THROW(); + } + PG_END_TRY(); + + return dict; +} + +/* + * Convert a Python object to a PostgreSQL tuple, using all supported + * conversion methods: tuple as a sequence, as a mapping or as an object that + * has __getattr__ support. + */ +HeapTuple +PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv) +{ + HeapTuple tuple; + + if (PySequence_Check(plrv)) + /* composite type as sequence (tuple, list etc) */ + tuple = PLySequence_ToTuple(info, desc, plrv); + else if (PyMapping_Check(plrv)) + /* composite type as mapping (currently only dict) */ + tuple = PLyMapping_ToTuple(info, desc, plrv); + else + /* returned as smth, must provide method __getattr__(name) */ + tuple = PLyGenericObject_ToTuple(info, desc, plrv); + + return tuple; +} + +static void +PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup) +{ + Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); + Oid element_type; + + perm_fmgr_info(typeStruct->typinput, &arg->typfunc); + arg->typoid = HeapTupleGetOid(typeTup); + arg->typmod = -1; + arg->typioparam = getTypeIOParam(typeTup); + arg->typbyval = typeStruct->typbyval; + + element_type = get_element_type(arg->typoid); + + /* + * Select a conversion function to convert Python objects to PostgreSQL + * datums. Most data types can go through the generic function. + */ + switch (getBaseType(element_type ? element_type : arg->typoid)) + { + case BOOLOID: + arg->func = PLyObject_ToBool; + break; + case BYTEAOID: + arg->func = PLyObject_ToBytea; + break; + default: + arg->func = PLyObject_ToDatum; + break; + } + + /* Composite types need their own input routine, though */ + if (typeStruct->typtype == TYPTYPE_COMPOSITE) + { + arg->func = PLyObject_ToComposite; + } + + if (element_type) + { + char dummy_delim; + Oid funcid; + + if (type_is_rowtype(element_type)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/Python functions cannot return type %s", + format_type_be(arg->typoid)), + errdetail("PL/Python does not support conversion to arrays of row types."))); + + arg->elm = PLy_malloc0(sizeof(*arg->elm)); + arg->elm->func = arg->func; + arg->func = PLySequence_ToArray; + + arg->elm->typoid = element_type; + arg->elm->typmod = -1; + get_type_io_data(element_type, IOFunc_input, + &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim, + &arg->elm->typioparam, &funcid); + perm_fmgr_info(funcid, &arg->elm->typfunc); + } +} + +static void +PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup) +{ + Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); + Oid element_type = get_element_type(typeOid); + + /* Get the type's conversion information */ + perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); + arg->typoid = HeapTupleGetOid(typeTup); + arg->typmod = -1; + arg->typioparam = getTypeIOParam(typeTup); + arg->typbyval = typeStruct->typbyval; + arg->typlen = typeStruct->typlen; + arg->typalign = typeStruct->typalign; + + /* Determine which kind of Python object we will convert to */ + switch (getBaseType(element_type ? element_type : typeOid)) + { + case BOOLOID: + arg->func = PLyBool_FromBool; + break; + case FLOAT4OID: + arg->func = PLyFloat_FromFloat4; + break; + case FLOAT8OID: + arg->func = PLyFloat_FromFloat8; + break; + case NUMERICOID: + arg->func = PLyFloat_FromNumeric; + break; + case INT2OID: + arg->func = PLyInt_FromInt16; + break; + case INT4OID: + arg->func = PLyInt_FromInt32; + break; + case INT8OID: + arg->func = PLyLong_FromInt64; + break; + case BYTEAOID: + arg->func = PLyBytes_FromBytea; + break; + default: + arg->func = PLyString_FromDatum; + break; + } + + if (element_type) + { + char dummy_delim; + Oid funcid; + + arg->elm = PLy_malloc0(sizeof(*arg->elm)); + arg->elm->func = arg->func; + arg->func = PLyList_FromArray; + arg->elm->typoid = element_type; + arg->elm->typmod = -1; + get_type_io_data(element_type, IOFunc_output, + &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim, + &arg->elm->typioparam, &funcid); + perm_fmgr_info(funcid, &arg->elm->typfunc); + } +} + +static PyObject * +PLyBool_FromBool(PLyDatumToOb *arg, Datum d) +{ + /* + * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for + * generating SQL from trigger functions, but those are only supported in + * Python >= 2.3, and we support older versions. + * http://docs.python.org/api/boolObjects.html + */ + if (DatumGetBool(d)) + return PyBool_FromLong(1); + return PyBool_FromLong(0); +} + +static PyObject * +PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d) +{ + return PyFloat_FromDouble(DatumGetFloat4(d)); +} + +static PyObject * +PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d) +{ + return PyFloat_FromDouble(DatumGetFloat8(d)); +} + +static PyObject * +PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d) +{ + /* + * Numeric is cast to a PyFloat: This results in a loss of precision Would + * it be better to cast to PyString? + */ + Datum f = DirectFunctionCall1(numeric_float8, d); + double x = DatumGetFloat8(f); + + return PyFloat_FromDouble(x); +} + +static PyObject * +PLyInt_FromInt16(PLyDatumToOb *arg, Datum d) +{ + return PyInt_FromLong(DatumGetInt16(d)); +} + +static PyObject * +PLyInt_FromInt32(PLyDatumToOb *arg, Datum d) +{ + return PyInt_FromLong(DatumGetInt32(d)); +} + +static PyObject * +PLyLong_FromInt64(PLyDatumToOb *arg, Datum d) +{ + /* on 32 bit platforms "long" may be too small */ + if (sizeof(int64) > sizeof(long)) + return PyLong_FromLongLong(DatumGetInt64(d)); + else + return PyLong_FromLong(DatumGetInt64(d)); +} + +static PyObject * +PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d) +{ + text *txt = DatumGetByteaP(d); + char *str = VARDATA(txt); + size_t size = VARSIZE(txt) - VARHDRSZ; + + return PyBytes_FromStringAndSize(str, size); +} + +static PyObject * +PLyString_FromDatum(PLyDatumToOb *arg, Datum d) +{ + char *x = OutputFunctionCall(&arg->typfunc, d); + PyObject *r = PyString_FromString(x); + + pfree(x); + return r; +} + +static PyObject * +PLyList_FromArray(PLyDatumToOb *arg, Datum d) +{ + ArrayType *array = DatumGetArrayTypeP(d); + PLyDatumToOb *elm = arg->elm; + PyObject *list; + int length; + int lbound; + int i; + + if (ARR_NDIM(array) == 0) + return PyList_New(0); + + if (ARR_NDIM(array) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert multidimensional array to Python list"), + errdetail("PL/Python only supports one-dimensional arrays."))); + + length = ARR_DIMS(array)[0]; + lbound = ARR_LBOUND(array)[0]; + list = PyList_New(length); + + for (i = 0; i < length; i++) + { + Datum elem; + bool isnull; + int offset; + + offset = lbound + i; + elem = array_ref(array, 1, &offset, arg->typlen, + elm->typlen, elm->typbyval, elm->typalign, + &isnull); + if (isnull) + { + Py_INCREF(Py_None); + PyList_SET_ITEM(list, i, Py_None); + } + else + PyList_SET_ITEM(list, i, elm->func(elm, elem)); + } + + return list; +} + +/* + * Convert a Python object to a PostgreSQL bool datum. This can't go + * through the generic conversion function, because Python attaches a + * Boolean value to everything, more things than the PostgreSQL bool + * type can parse. + */ +static Datum +PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + Datum rv; + + Assert(plrv != Py_None); + rv = BoolGetDatum(PyObject_IsTrue(plrv)); + + if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN) + domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt); + + return rv; +} + +/* + * Convert a Python object to a PostgreSQL bytea datum. This doesn't + * go through the generic conversion function to circumvent problems + * with embedded nulls. And it's faster this way. + */ +static Datum +PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + PyObject *volatile plrv_so = NULL; + Datum rv; + + Assert(plrv != Py_None); + + plrv_so = PyObject_Bytes(plrv); + if (!plrv_so) + PLy_elog(ERROR, "could not create bytes representation of Python object"); + + PG_TRY(); + { + char *plrv_sc = PyBytes_AsString(plrv_so); + size_t len = PyBytes_Size(plrv_so); + size_t size = len + VARHDRSZ; + bytea *result = palloc(size); + + SET_VARSIZE(result, size); + memcpy(VARDATA(result), plrv_sc, len); + rv = PointerGetDatum(result); + } + PG_CATCH(); + { + Py_XDECREF(plrv_so); + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_XDECREF(plrv_so); + + if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN) + domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt); + + return rv; +} + + +/* + * Convert a Python object to a composite type. First look up the type's + * description, then route the Python object through the conversion function + * for obtaining PostgreSQL tuples. + */ +static Datum +PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + HeapTuple tuple = NULL; + Datum rv; + PLyTypeInfo info; + TupleDesc desc; + + if (typmod != -1) + elog(ERROR, "received unnamed record type as input"); + + /* Create a dummy PLyTypeInfo */ + MemSet(&info, 0, sizeof(PLyTypeInfo)); + PLy_typeinfo_init(&info); + /* Mark it as needing output routines lookup */ + info.is_rowtype = 2; + + desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); + + /* + * This will set up the dummy PLyTypeInfo's output conversion routines, + * since we left is_rowtype as 2. A future optimisation could be caching + * that info instead of looking it up every time a tuple is returned from + * the function. + */ + tuple = PLyObject_ToTuple(&info, desc, plrv); + + PLy_typeinfo_dealloc(&info); + + if (tuple != NULL) + rv = HeapTupleGetDatum(tuple); + else + rv = (Datum) NULL; + + return rv; +} + + +/* + * Generic conversion function: Convert PyObject to cstring and + * cstring into PostgreSQL type. + */ +static Datum +PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + PyObject *volatile plrv_bo = NULL; + Datum rv; + + Assert(plrv != Py_None); + + if (PyUnicode_Check(plrv)) + plrv_bo = PLyUnicode_Bytes(plrv); + else + { +#if PY_MAJOR_VERSION >= 3 + PyObject *s = PyObject_Str(plrv); + + plrv_bo = PLyUnicode_Bytes(s); + Py_XDECREF(s); +#else + plrv_bo = PyObject_Str(plrv); +#endif + } + if (!plrv_bo) + PLy_elog(ERROR, "could not create string representation of Python object"); + + PG_TRY(); + { + char *plrv_sc = PyBytes_AsString(plrv_bo); + size_t plen = PyBytes_Size(plrv_bo); + size_t slen = strlen(plrv_sc); + + if (slen < plen) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes"))); + else if (slen > plen) + elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length"); + pg_verifymbstr(plrv_sc, slen, false); + rv = InputFunctionCall(&arg->typfunc, + plrv_sc, + arg->typioparam, + typmod); + } + PG_CATCH(); + { + Py_XDECREF(plrv_bo); + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_XDECREF(plrv_bo); + + return rv; +} + +static Datum +PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) +{ + ArrayType *array; + int i; + Datum *elems; + bool *nulls; + int len; + int lbs; + + Assert(plrv != Py_None); + + if (!PySequence_Check(plrv)) + PLy_elog(ERROR, "return value of function with array return type is not a Python sequence"); + + len = PySequence_Length(plrv); + elems = palloc(sizeof(*elems) * len); + nulls = palloc(sizeof(*nulls) * len); + + for (i = 0; i < len; i++) + { + PyObject *obj = PySequence_GetItem(plrv, i); + + if (obj == Py_None) + nulls[i] = true; + else + { + nulls[i] = false; + + /* + * We don't support arrays of row types yet, so the first argument + * can be NULL. + */ + elems[i] = arg->elm->func(arg->elm, -1, obj); + } + Py_XDECREF(obj); + } + + lbs = 1; + array = construct_md_array(elems, nulls, 1, &len, &lbs, + get_element_type(arg->typoid), arg->elm->typlen, arg->elm->typbyval, arg->elm->typalign); + return PointerGetDatum(array); +} + +static HeapTuple +PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) +{ + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + Assert(PyMapping_Check(mapping)); + + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + char *key; + PyObject *volatile value; + PLyObToDatum *att; + + if (desc->attrs[i]->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + key = NameStr(desc->attrs[i]->attname); + value = NULL; + att = &info->out.r.atts[i]; + PG_TRY(); + { + value = PyMapping_GetItemString(mapping, key); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + values[i] = (att->func) (att, -1, value); + nulls[i] = false; + } + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("key \"%s\" not found in mapping", key), + errhint("To return null in a column, " + "add the value None to the mapping with the key named after the column."))); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; +} + + +static HeapTuple +PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) +{ + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int idx; + volatile int i; + + Assert(PySequence_Check(sequence)); + + /* + * Check that sequence length is exactly same as PG tuple's. We actually + * can ignore exceeding items or assume missing ones as null but to avoid + * plpython developer's errors we are strict here + */ + idx = 0; + for (i = 0; i < desc->natts; i++) + { + if (!desc->attrs[i]->attisdropped) + idx++; + } + if (PySequence_Length(sequence) != idx) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("length of returned sequence did not match number of columns in row"))); + + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + idx = 0; + for (i = 0; i < desc->natts; ++i) + { + PyObject *volatile value; + PLyObToDatum *att; + + if (desc->attrs[i]->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + value = NULL; + att = &info->out.r.atts[i]; + PG_TRY(); + { + value = PySequence_GetItem(sequence, idx); + Assert(value); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + values[i] = (att->func) (att, -1, value); + nulls[i] = false; + } + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + + idx++; + } + + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; +} + + +static HeapTuple +PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object) +{ + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + if (info->is_rowtype == 2) + PLy_output_tuple_funcs(info, desc); + Assert(info->is_rowtype == 1); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + char *key; + PyObject *volatile value; + PLyObToDatum *att; + + if (desc->attrs[i]->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + key = NameStr(desc->attrs[i]->attname); + value = NULL; + att = &info->out.r.atts[i]; + PG_TRY(); + { + value = PyObject_GetAttrString(object, key); + if (value == Py_None) + { + values[i] = (Datum) NULL; + nulls[i] = true; + } + else if (value) + { + values[i] = (att->func) (att, -1, value); + nulls[i] = false; + } + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" does not exist in Python object", key), + errhint("To return null in a column, " + "let the returned object have an attribute named " + "after column with value None."))); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_form_tuple(desc, values, nulls); + ReleaseTupleDesc(desc); + pfree(values); + pfree(nulls); + + return tuple; +} + +/* + * This routine is a crock, and so is everyplace that calls it. The problem + * is that the cached form of plpython functions/queries is allocated permanently + * (mostly via malloc()) and never released until backend exit. Subsidiary + * data structures such as fmgr info records therefore must live forever + * as well. A better implementation would store all this stuff in a per- + * function memory context that could be reclaimed at need. In the meantime, + * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever + * it might allocate, and whatever the eventual function might allocate using + * fn_mcxt, will live forever too. + */ +static void +perm_fmgr_info(Oid functionId, FmgrInfo *finfo) +{ + fmgr_info_cxt(functionId, finfo, TopMemoryContext); +} diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h new file mode 100644 index 0000000000..6708bf9c9b --- /dev/null +++ b/src/pl/plpython/plpy_typeio.h @@ -0,0 +1,107 @@ +/* + * src/pl/plpython/plpy_typeio.h + */ + +#ifndef PLPY_TYPEIO_H +#define PLPY_TYPEIO_H + +#include "access/htup.h" +#include "fmgr.h" +#include "storage/itemptr.h" + +struct PLyDatumToOb; +typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *, Datum); + +typedef struct PLyDatumToOb +{ + PLyDatumToObFunc func; + FmgrInfo typfunc; /* The type's output function */ + Oid typoid; /* The OID of the type */ + int32 typmod; /* The typmod of the type */ + Oid typioparam; + bool typbyval; + int16 typlen; + char typalign; + struct PLyDatumToOb *elm; +} PLyDatumToOb; + +typedef struct PLyTupleToOb +{ + PLyDatumToOb *atts; + int natts; +} PLyTupleToOb; + +typedef union PLyTypeInput +{ + PLyDatumToOb d; + PLyTupleToOb r; +} PLyTypeInput; + +/* convert PyObject to a Postgresql Datum or tuple. + * output from Python + */ +struct PLyObToDatum; +typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *, int32, PyObject *); + +typedef struct PLyObToDatum +{ + PLyObToDatumFunc func; + FmgrInfo typfunc; /* The type's input function */ + Oid typoid; /* The OID of the type */ + int32 typmod; /* The typmod of the type */ + Oid typioparam; + bool typbyval; + int16 typlen; + char typalign; + struct PLyObToDatum *elm; +} PLyObToDatum; + +typedef struct PLyObToTuple +{ + PLyObToDatum *atts; + int natts; +} PLyObToTuple; + +typedef union PLyTypeOutput +{ + PLyObToDatum d; + PLyObToTuple r; +} PLyTypeOutput; + +/* all we need to move Postgresql data to Python objects, + * and vice versa + */ +typedef struct PLyTypeInfo +{ + PLyTypeInput in; + PLyTypeOutput out; + + /* + * is_rowtype can be: -1 = not known yet (initial state); 0 = scalar + * datatype; 1 = rowtype; 2 = rowtype, but I/O functions not set up yet + */ + int is_rowtype; + /* used to check if the type has been modified */ + Oid typ_relid; + TransactionId typrel_xmin; + ItemPointerData typrel_tid; +} PLyTypeInfo; + +extern void PLy_typeinfo_init(PLyTypeInfo *); +extern void PLy_typeinfo_dealloc(PLyTypeInfo *); + +extern void PLy_input_datum_func(PLyTypeInfo *, Oid, HeapTuple); +extern void PLy_output_datum_func(PLyTypeInfo *, HeapTuple); + +extern void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); +extern void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc); + +extern void PLy_output_record_funcs(PLyTypeInfo *, TupleDesc); + +/* conversion from Python objects to heap tuples */ +extern HeapTuple PLyObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); + +/* conversion from heap tuples to Python dictionaries */ +extern PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); + +#endif /* PLPY_TYPEIO_H */ diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c new file mode 100644 index 0000000000..414b9d5445 --- /dev/null +++ b/src/pl/plpython/plpy_util.c @@ -0,0 +1,125 @@ +/* + * utility functions + * + * src/pl/plpython/plpy_util.c + */ + +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "utils/memutils.h" +#include "utils/palloc.h" + +#include "plpython.h" + +#include "plpy_util.h" + +#include "plpy_elog.h" + + +void * +PLy_malloc(size_t bytes) +{ + /* We need our allocations to be long-lived, so use TopMemoryContext */ + return MemoryContextAlloc(TopMemoryContext, bytes); +} + +void * +PLy_malloc0(size_t bytes) +{ + void *ptr = PLy_malloc(bytes); + + MemSet(ptr, 0, bytes); + return ptr; +} + +char * +PLy_strdup(const char *str) +{ + char *result; + size_t len; + + len = strlen(str) + 1; + result = PLy_malloc(len); + memcpy(result, str, len); + + return result; +} + +/* define this away */ +void +PLy_free(void *ptr) +{ + pfree(ptr); +} + +/* + * Convert a Python unicode object to a Python string/bytes object in + * PostgreSQL server encoding. Reference ownership is passed to the + * caller. + */ +PyObject * +PLyUnicode_Bytes(PyObject *unicode) +{ + PyObject *rv; + const char *serverenc; + + /* + * Python understands almost all PostgreSQL encoding names, but it doesn't + * know SQL_ASCII. + */ + if (GetDatabaseEncoding() == PG_SQL_ASCII) + serverenc = "ascii"; + else + serverenc = GetDatabaseEncodingName(); + rv = PyUnicode_AsEncodedString(unicode, serverenc, "strict"); + if (rv == NULL) + PLy_elog(ERROR, "could not convert Python Unicode object to PostgreSQL server encoding"); + return rv; +} + +/* + * Convert a Python unicode object to a C string in PostgreSQL server + * encoding. No Python object reference is passed out of this + * function. The result is palloc'ed. + * + * Note that this function is disguised as PyString_AsString() when + * using Python 3. That function retuns a pointer into the internal + * memory of the argument, which isn't exactly the interface of this + * function. But in either case you get a rather short-lived + * reference that you ought to better leave alone. + */ +char * +PLyUnicode_AsString(PyObject *unicode) +{ + PyObject *o = PLyUnicode_Bytes(unicode); + char *rv = pstrdup(PyBytes_AsString(o)); + + Py_XDECREF(o); + return rv; +} + +#if PY_MAJOR_VERSION >= 3 +/* + * Convert a C string in the PostgreSQL server encoding to a Python + * unicode object. Reference ownership is passed to the caller. + */ +PyObject * +PLyUnicode_FromString(const char *s) +{ + char *utf8string; + PyObject *o; + + utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, + strlen(s), + GetDatabaseEncoding(), + PG_UTF8); + + o = PyUnicode_FromString(utf8string); + + if (utf8string != s) + pfree(utf8string); + + return o; +} +#endif /* PY_MAJOR_VERSION >= 3 */ diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h new file mode 100644 index 0000000000..237d6c5751 --- /dev/null +++ b/src/pl/plpython/plpy_util.h @@ -0,0 +1,21 @@ +/*-------------------------- + * common utility functions + *-------------------------- + */ + +#ifndef PLPY_UTIL_H +#define PLPY_UTIL_H + +extern void *PLy_malloc(size_t); +extern void *PLy_malloc0(size_t); +extern char *PLy_strdup(const char *); +extern void PLy_free(void *); + +extern PyObject *PLyUnicode_Bytes(PyObject *unicode); +extern char *PLyUnicode_AsString(PyObject *unicode); + +#if PY_MAJOR_VERSION >= 3 +extern PyObject *PLyUnicode_FromString(const char *s); +#endif + +#endif /* PLPY_UTIL_H */ diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c deleted file mode 100644 index dce8ff247b..0000000000 --- a/src/pl/plpython/plpython.c +++ /dev/null @@ -1,5439 +0,0 @@ -/********************************************************************** - * plpython.c - python as a procedural language for PostgreSQL - * - * src/pl/plpython/plpython.c - * - ********************************************************************* - */ - -#include "postgres.h" - -/* system stuff */ -#include -#include - -/* 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 -#undef errcode -#define _DEBUG -#elif defined (_MSC_VER) -#define errcode __msvc_errcode -#include -#undef errcode -#else -#include -#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 -#include - -/* 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, "", 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(); - * { - * - * PLy_spi_subtransaction_commit(oldcontext, oldowner); - * } - * PG_CATCH(); - * { - * - * 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 - * . - */ -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 ""; - 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 , 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 . - */ - if (*tb_depth == 1) - fname = ""; - 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 "" as the - * filename - */ - if (PLy_curr_procedure && plain_filename != NULL && - strcmp(plain_filename, "") == 0) - { - /* - * If we know the current procedure, append the exact line - * from the source, again mimicking Python's traceback.py - * module behavior. We could store the already line-split - * source to avoid splitting it every time, but producing a - * traceback is not the most important scenario to optimize - * for. But we do not go as far as traceback.py in reading - * the source of imported modules. - */ - line = get_source_line(PLy_curr_procedure->src, plain_lineno); - if (line) - { - appendStringInfo(&tbstr, "\n %s", line); - pfree(line); - } - } - } - - Py_DECREF(frame); - Py_DECREF(code); - Py_DECREF(name); - Py_DECREF(lineno); - Py_DECREF(filename); - - /* Release the current frame and go to the next one. */ - tb_prev = tb; - tb = PyObject_GetAttrString(tb, "tb_next"); - Assert(tb_prev != Py_None); - Py_DECREF(tb_prev); - if (tb == NULL) - elog(ERROR, "could not traverse Python traceback"); - (*tb_depth)++; - } - - /* Return the traceback. */ - *tbmsg = tbstr.data; - - Py_XDECREF(e_type_o); - Py_XDECREF(e_module_o); - Py_XDECREF(vob); - Py_XDECREF(v); - Py_DECREF(e); -} - -/* python module code */ - -/* some dumb utility functions */ -static void * -PLy_malloc(size_t bytes) -{ - /* We need our allocations to be long-lived, so use TopMemoryContext */ - return MemoryContextAlloc(TopMemoryContext, bytes); -} - -static void * -PLy_malloc0(size_t bytes) -{ - void *ptr = PLy_malloc(bytes); - - MemSet(ptr, 0, bytes); - return ptr; -} - -static char * -PLy_strdup(const char *str) -{ - char *result; - size_t len; - - len = strlen(str) + 1; - result = PLy_malloc(len); - memcpy(result, str, len); - - return result; -} - -/* define this away */ -static void -PLy_free(void *ptr) -{ - pfree(ptr); -} - -/* - * Convert a Python unicode object to a Python string/bytes object in - * PostgreSQL server encoding. Reference ownership is passed to the - * caller. - */ -static PyObject * -PLyUnicode_Bytes(PyObject *unicode) -{ - PyObject *rv; - const char *serverenc; - - /* - * Python understands almost all PostgreSQL encoding names, but it doesn't - * know SQL_ASCII. - */ - if (GetDatabaseEncoding() == PG_SQL_ASCII) - serverenc = "ascii"; - else - serverenc = GetDatabaseEncodingName(); - rv = PyUnicode_AsEncodedString(unicode, serverenc, "strict"); - if (rv == NULL) - PLy_elog(ERROR, "could not convert Python Unicode object to PostgreSQL server encoding"); - return rv; -} - -/* - * Convert a Python unicode object to a C string in PostgreSQL server - * encoding. No Python object reference is passed out of this - * function. The result is palloc'ed. - * - * Note that this function is disguised as PyString_AsString() when - * using Python 3. That function retuns a pointer into the internal - * memory of the argument, which isn't exactly the interface of this - * function. But in either case you get a rather short-lived - * reference that you ought to better leave alone. - */ -static char * -PLyUnicode_AsString(PyObject *unicode) -{ - PyObject *o = PLyUnicode_Bytes(unicode); - char *rv = pstrdup(PyBytes_AsString(o)); - - Py_XDECREF(o); - return rv; -} - -#if PY_MAJOR_VERSION >= 3 -/* - * Convert a C string in the PostgreSQL server encoding to a Python - * unicode object. Reference ownership is passed to the caller. - */ -static PyObject * -PLyUnicode_FromString(const char *s) -{ - char *utf8string; - PyObject *o; - - utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, - strlen(s), - GetDatabaseEncoding(), - PG_UTF8); - - o = PyUnicode_FromString(utf8string); - - if (utf8string != s) - pfree(utf8string); - - return o; -} -#endif /* PY_MAJOR_VERSION >= 3 */ - -#if PY_MAJOR_VERSION < 3 - -/* Define aliases plpython2_call_handler etc */ -Datum plpython2_call_handler(PG_FUNCTION_ARGS); -Datum plpython2_inline_handler(PG_FUNCTION_ARGS); -Datum plpython2_validator(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1(plpython2_call_handler); - -Datum -plpython2_call_handler(PG_FUNCTION_ARGS) -{ - return plpython_call_handler(fcinfo); -} - -PG_FUNCTION_INFO_V1(plpython2_inline_handler); - -Datum -plpython2_inline_handler(PG_FUNCTION_ARGS) -{ - return plpython_inline_handler(fcinfo); -} - -PG_FUNCTION_INFO_V1(plpython2_validator); - -Datum -plpython2_validator(PG_FUNCTION_ARGS) -{ - return plpython_validator(fcinfo); -} - -#endif /* PY_MAJOR_VERSION < 3 */ diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h new file mode 100644 index 0000000000..ab2983c16d --- /dev/null +++ b/src/pl/plpython/plpython.h @@ -0,0 +1,156 @@ +/*------------------------------------------------------------------------- + * + * plpython.h - Python as a procedural language for PostgreSQL + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/pl/plpython/plpython.h + * + *------------------------------------------------------------------------- + */ +#ifndef PLPYTHON_H +#define PLPYTHON_H + +/* + * Include order should be: postgres.h, other postgres headers, plpython.h, + * other plpython headers + */ +#ifndef POSTGRES_H +#error postgres.h must be included before plpython.h +#endif + +/* + * Undefine some things that get (re)defined in the Python headers. They aren't + * used by the PL/Python code, and all PostgreSQL headers should be included + * earlier, so this should be pretty safe. + */ +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#undef HAVE_STRERROR +#undef HAVE_TZNAME + +/* + * Sometimes python carefully scribbles on our *printf macros. + * So we undefine them here and redefine them after it's done its dirty deed. + */ + +#ifdef USE_REPL_SNPRINTF +#undef snprintf +#undef vsnprintf +#endif + +#if defined(_MSC_VER) && defined(_DEBUG) +/* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ +#undef _DEBUG +/* Also hide away errcode, since we load Python.h before postgres.h */ +#define errcode __msvc_errcode +#include +#undef errcode +#define _DEBUG +#elif defined (_MSC_VER) +#define errcode __msvc_errcode +#include +#undef errcode +#else +#include +#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 +#include + +/* 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 */