mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-21 08:29:39 +08:00
Split plpython.c into smaller pieces
This moves the code around from one huge file into hopefully logical and more manageable modules. For the most part, the code itself was not touched, except: PLy_function_handler and PLy_trigger_handler were renamed to PLy_exec_function and PLy_exec_trigger, because they were not actually handlers in the PL handler sense, and it makes the naming more similar to the way PL/pgSQL is organized. The initialization of the procedure caches was separated into a new function init_procedure_caches to keep the hash tables private to plpy_procedures.c. Jan Urbański and Peter Eisentraut
This commit is contained in:
parent
59e242a496
commit
147c248254
@ -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 $< > $@
|
||||
|
@ -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 \
|
||||
|
492
src/pl/plpython/plpy_cursorobject.c
Normal file
492
src/pl/plpython/plpy_cursorobject.c
Normal file
@ -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;
|
||||
}
|
22
src/pl/plpython/plpy_cursorobject.h
Normal file
22
src/pl/plpython/plpy_cursorobject.h
Normal file
@ -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 */
|
428
src/pl/plpython/plpy_elog.c
Normal file
428
src/pl/plpython/plpy_elog.c
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
* reporting Python exceptions as PostgreSQL errors
|
||||
*
|
||||
* src/pl/plpython/plpy_elog.c
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "lib/stringinfo.h"
|
||||
|
||||
#include "plpython.h"
|
||||
|
||||
#include "plpy_elog.h"
|
||||
|
||||
#include "plpy_procedure.h"
|
||||
|
||||
|
||||
PyObject *PLy_exc_error = NULL;
|
||||
PyObject *PLy_exc_fatal = NULL;
|
||||
PyObject *PLy_exc_spi_error = NULL;
|
||||
|
||||
|
||||
static void PLy_traceback(char **, char **, int *);
|
||||
static void PLy_get_spi_error_data(PyObject *, int *, char **,
|
||||
char **, char **, int *);
|
||||
static char * get_source_line(const char *, int);
|
||||
|
||||
|
||||
/*
|
||||
* Emit a PG error or notice, together with any available info about
|
||||
* the current Python error, previously set by PLy_exception_set().
|
||||
* This should be used to propagate Python errors into PG. If fmt is
|
||||
* NULL, the Python error becomes the primary error message, otherwise
|
||||
* it becomes the detail. If there is a Python traceback, it is put
|
||||
* in the context.
|
||||
*/
|
||||
void
|
||||
PLy_elog(int elevel, const char *fmt,...)
|
||||
{
|
||||
char *xmsg;
|
||||
char *tbmsg;
|
||||
int tb_depth;
|
||||
StringInfoData emsg;
|
||||
PyObject *exc,
|
||||
*val,
|
||||
*tb;
|
||||
const char *primary = NULL;
|
||||
int sqlerrcode = 0;
|
||||
char *detail = NULL;
|
||||
char *hint = NULL;
|
||||
char *query = NULL;
|
||||
int position = 0;
|
||||
|
||||
PyErr_Fetch(&exc, &val, &tb);
|
||||
if (exc != NULL)
|
||||
{
|
||||
if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
|
||||
PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position);
|
||||
else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
|
||||
elevel = FATAL;
|
||||
}
|
||||
PyErr_Restore(exc, val, tb);
|
||||
|
||||
PLy_traceback(&xmsg, &tbmsg, &tb_depth);
|
||||
|
||||
if (fmt)
|
||||
{
|
||||
initStringInfo(&emsg);
|
||||
for (;;)
|
||||
{
|
||||
va_list ap;
|
||||
bool success;
|
||||
|
||||
va_start(ap, fmt);
|
||||
success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
|
||||
va_end(ap);
|
||||
if (success)
|
||||
break;
|
||||
enlargeStringInfo(&emsg, emsg.maxlen);
|
||||
}
|
||||
primary = emsg.data;
|
||||
|
||||
/* Since we have a format string, we cannot have a SPI detail. */
|
||||
Assert(detail == NULL);
|
||||
|
||||
/* If there's an exception message, it goes in the detail. */
|
||||
if (xmsg)
|
||||
detail = xmsg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xmsg)
|
||||
primary = xmsg;
|
||||
}
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR),
|
||||
errmsg_internal("%s", primary ? primary : "no exception data"),
|
||||
(detail) ? errdetail_internal("%s", detail) : 0,
|
||||
(tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
|
||||
(hint) ? errhint("%s", hint) : 0,
|
||||
(query) ? internalerrquery(query) : 0,
|
||||
(position) ? internalerrposition(position) : 0));
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
if (fmt)
|
||||
pfree(emsg.data);
|
||||
if (xmsg)
|
||||
pfree(xmsg);
|
||||
if (tbmsg)
|
||||
pfree(tbmsg);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
if (fmt)
|
||||
pfree(emsg.data);
|
||||
if (xmsg)
|
||||
pfree(xmsg);
|
||||
if (tbmsg)
|
||||
pfree(tbmsg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract a Python traceback from the current exception.
|
||||
*
|
||||
* The exception error message is returned in xmsg, the traceback in
|
||||
* tbmsg (both as palloc'd strings) and the traceback depth in
|
||||
* tb_depth.
|
||||
*/
|
||||
static void
|
||||
PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth)
|
||||
{
|
||||
PyObject *e,
|
||||
*v,
|
||||
*tb;
|
||||
PyObject *e_type_o;
|
||||
PyObject *e_module_o;
|
||||
char *e_type_s = NULL;
|
||||
char *e_module_s = NULL;
|
||||
PyObject *vob = NULL;
|
||||
char *vstr;
|
||||
StringInfoData xstr;
|
||||
StringInfoData tbstr;
|
||||
|
||||
/*
|
||||
* get the current exception
|
||||
*/
|
||||
PyErr_Fetch(&e, &v, &tb);
|
||||
|
||||
/*
|
||||
* oops, no exception, return
|
||||
*/
|
||||
if (e == NULL)
|
||||
{
|
||||
*xmsg = NULL;
|
||||
*tbmsg = NULL;
|
||||
*tb_depth = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PyErr_NormalizeException(&e, &v, &tb);
|
||||
|
||||
/*
|
||||
* Format the exception and its value and put it in xmsg.
|
||||
*/
|
||||
|
||||
e_type_o = PyObject_GetAttrString(e, "__name__");
|
||||
e_module_o = PyObject_GetAttrString(e, "__module__");
|
||||
if (e_type_o)
|
||||
e_type_s = PyString_AsString(e_type_o);
|
||||
if (e_type_s)
|
||||
e_module_s = PyString_AsString(e_module_o);
|
||||
|
||||
if (v && ((vob = PyObject_Str(v)) != NULL))
|
||||
vstr = PyString_AsString(vob);
|
||||
else
|
||||
vstr = "unknown";
|
||||
|
||||
initStringInfo(&xstr);
|
||||
if (!e_type_s || !e_module_s)
|
||||
{
|
||||
if (PyString_Check(e))
|
||||
/* deprecated string exceptions */
|
||||
appendStringInfoString(&xstr, PyString_AsString(e));
|
||||
else
|
||||
/* shouldn't happen */
|
||||
appendStringInfoString(&xstr, "unrecognized exception");
|
||||
}
|
||||
/* mimics behavior of traceback.format_exception_only */
|
||||
else if (strcmp(e_module_s, "builtins") == 0
|
||||
|| strcmp(e_module_s, "__main__") == 0
|
||||
|| strcmp(e_module_s, "exceptions") == 0)
|
||||
appendStringInfo(&xstr, "%s", e_type_s);
|
||||
else
|
||||
appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
|
||||
appendStringInfo(&xstr, ": %s", vstr);
|
||||
|
||||
*xmsg = xstr.data;
|
||||
|
||||
/*
|
||||
* Now format the traceback and put it in tbmsg.
|
||||
*/
|
||||
|
||||
*tb_depth = 0;
|
||||
initStringInfo(&tbstr);
|
||||
/* Mimick Python traceback reporting as close as possible. */
|
||||
appendStringInfoString(&tbstr, "Traceback (most recent call last):");
|
||||
while (tb != NULL && tb != Py_None)
|
||||
{
|
||||
PyObject *volatile tb_prev = NULL;
|
||||
PyObject *volatile frame = NULL;
|
||||
PyObject *volatile code = NULL;
|
||||
PyObject *volatile name = NULL;
|
||||
PyObject *volatile lineno = NULL;
|
||||
PyObject *volatile filename = NULL;
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
lineno = PyObject_GetAttrString(tb, "tb_lineno");
|
||||
if (lineno == NULL)
|
||||
elog(ERROR, "could not get line number from Python traceback");
|
||||
|
||||
frame = PyObject_GetAttrString(tb, "tb_frame");
|
||||
if (frame == NULL)
|
||||
elog(ERROR, "could not get frame from Python traceback");
|
||||
|
||||
code = PyObject_GetAttrString(frame, "f_code");
|
||||
if (code == NULL)
|
||||
elog(ERROR, "could not get code object from Python frame");
|
||||
|
||||
name = PyObject_GetAttrString(code, "co_name");
|
||||
if (name == NULL)
|
||||
elog(ERROR, "could not get function name from Python code object");
|
||||
|
||||
filename = PyObject_GetAttrString(code, "co_filename");
|
||||
if (filename == NULL)
|
||||
elog(ERROR, "could not get file name from Python code object");
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
Py_XDECREF(frame);
|
||||
Py_XDECREF(code);
|
||||
Py_XDECREF(name);
|
||||
Py_XDECREF(lineno);
|
||||
Py_XDECREF(filename);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
/* The first frame always points at <module>, skip it. */
|
||||
if (*tb_depth > 0)
|
||||
{
|
||||
char *proname;
|
||||
char *fname;
|
||||
char *line;
|
||||
char *plain_filename;
|
||||
long plain_lineno;
|
||||
|
||||
/*
|
||||
* The second frame points at the internal function, but to mimick
|
||||
* Python error reporting we want to say <module>.
|
||||
*/
|
||||
if (*tb_depth == 1)
|
||||
fname = "<module>";
|
||||
else
|
||||
fname = PyString_AsString(name);
|
||||
|
||||
proname = PLy_procedure_name(PLy_curr_procedure);
|
||||
plain_filename = PyString_AsString(filename);
|
||||
plain_lineno = PyInt_AsLong(lineno);
|
||||
|
||||
if (proname == NULL)
|
||||
appendStringInfo(
|
||||
&tbstr, "\n PL/Python anonymous code block, line %ld, in %s",
|
||||
plain_lineno - 1, fname);
|
||||
else
|
||||
appendStringInfo(
|
||||
&tbstr, "\n PL/Python function \"%s\", line %ld, in %s",
|
||||
proname, plain_lineno - 1, fname);
|
||||
|
||||
/*
|
||||
* function code object was compiled with "<string>" as the
|
||||
* filename
|
||||
*/
|
||||
if (PLy_curr_procedure && plain_filename != NULL &&
|
||||
strcmp(plain_filename, "<string>") == 0)
|
||||
{
|
||||
/*
|
||||
* If we know the current procedure, append the exact line
|
||||
* from the source, again mimicking Python's traceback.py
|
||||
* module behavior. We could store the already line-split
|
||||
* source to avoid splitting it every time, but producing a
|
||||
* traceback is not the most important scenario to optimize
|
||||
* for. But we do not go as far as traceback.py in reading
|
||||
* the source of imported modules.
|
||||
*/
|
||||
line = get_source_line(PLy_curr_procedure->src, plain_lineno);
|
||||
if (line)
|
||||
{
|
||||
appendStringInfo(&tbstr, "\n %s", line);
|
||||
pfree(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(frame);
|
||||
Py_DECREF(code);
|
||||
Py_DECREF(name);
|
||||
Py_DECREF(lineno);
|
||||
Py_DECREF(filename);
|
||||
|
||||
/* Release the current frame and go to the next one. */
|
||||
tb_prev = tb;
|
||||
tb = PyObject_GetAttrString(tb, "tb_next");
|
||||
Assert(tb_prev != Py_None);
|
||||
Py_DECREF(tb_prev);
|
||||
if (tb == NULL)
|
||||
elog(ERROR, "could not traverse Python traceback");
|
||||
(*tb_depth)++;
|
||||
}
|
||||
|
||||
/* Return the traceback. */
|
||||
*tbmsg = tbstr.data;
|
||||
|
||||
Py_XDECREF(e_type_o);
|
||||
Py_XDECREF(e_module_o);
|
||||
Py_XDECREF(vob);
|
||||
Py_XDECREF(v);
|
||||
Py_DECREF(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the error data from a SPIError
|
||||
*/
|
||||
static void
|
||||
PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position)
|
||||
{
|
||||
PyObject *spidata = NULL;
|
||||
|
||||
spidata = PyObject_GetAttrString(exc, "spidata");
|
||||
if (!spidata)
|
||||
goto cleanup;
|
||||
|
||||
if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position))
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
PyErr_Clear();
|
||||
/* no elog here, we simply won't report the errhint, errposition etc */
|
||||
Py_XDECREF(spidata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the given source line as a palloc'd string
|
||||
*/
|
||||
static char *
|
||||
get_source_line(const char *src, int lineno)
|
||||
{
|
||||
const char *s = NULL;
|
||||
const char *next = src;
|
||||
int current = 0;
|
||||
|
||||
while (current < lineno)
|
||||
{
|
||||
s = next;
|
||||
next = strchr(s + 1, '\n');
|
||||
current++;
|
||||
if (next == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if (current != lineno)
|
||||
return NULL;
|
||||
|
||||
while (*s && isspace((unsigned char) *s))
|
||||
s++;
|
||||
|
||||
if (next == NULL)
|
||||
return pstrdup(s);
|
||||
|
||||
/*
|
||||
* Sanity check, next < s if the line was all-whitespace, which should
|
||||
* never happen if Python reported a frame created on that line, but check
|
||||
* anyway.
|
||||
*/
|
||||
if (next < s)
|
||||
return NULL;
|
||||
|
||||
return pnstrdup(s, next - s);
|
||||
}
|
||||
|
||||
|
||||
/* call PyErr_SetString with a vprint interface and translation support */
|
||||
void
|
||||
PLy_exception_set(PyObject *exc, const char *fmt,...)
|
||||
{
|
||||
char buf[1024];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
|
||||
va_end(ap);
|
||||
|
||||
PyErr_SetString(exc, buf);
|
||||
}
|
||||
|
||||
/* same, with pluralized message */
|
||||
void
|
||||
PLy_exception_set_plural(PyObject *exc,
|
||||
const char *fmt_singular, const char *fmt_plural,
|
||||
unsigned long n,...)
|
||||
{
|
||||
char buf[1024];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, n);
|
||||
vsnprintf(buf, sizeof(buf),
|
||||
dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
|
||||
ap);
|
||||
va_end(ap);
|
||||
|
||||
PyErr_SetString(exc, buf);
|
||||
}
|
24
src/pl/plpython/plpy_elog.h
Normal file
24
src/pl/plpython/plpy_elog.h
Normal file
@ -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 */
|
859
src/pl/plpython/plpy_exec.c
Normal file
859
src/pl/plpython/plpy_exec.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
13
src/pl/plpython/plpy_exec.h
Normal file
13
src/pl/plpython/plpy_exec.h
Normal file
@ -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 */
|
325
src/pl/plpython/plpy_main.c
Normal file
325
src/pl/plpython/plpy_main.c
Normal file
@ -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");
|
||||
}
|
13
src/pl/plpython/plpy_main.h
Normal file
13
src/pl/plpython/plpy_main.h
Normal file
@ -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 */
|
128
src/pl/plpython/plpy_planobject.c
Normal file
128
src/pl/plpython/plpy_planobject.c
Normal file
@ -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;
|
||||
}
|
26
src/pl/plpython/plpy_planobject.h
Normal file
26
src/pl/plpython/plpy_planobject.h
Normal file
@ -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 */
|
417
src/pl/plpython/plpy_plpymodule.c
Normal file
417
src/pl/plpython/plpy_plpymodule.c
Normal file
@ -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;
|
||||
}
|
19
src/pl/plpython/plpy_plpymodule.h
Normal file
19
src/pl/plpython/plpy_plpymodule.h
Normal file
@ -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 */
|
531
src/pl/plpython/plpy_procedure.c
Normal file
531
src/pl/plpython/plpy_procedure.c
Normal file
@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Python procedure manipulation for plpython
|
||||
*
|
||||
* src/pl/plpython/plpy_procedure.c
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/transam.h"
|
||||
#include "funcapi.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/hsearch.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "plpython.h"
|
||||
|
||||
#include "plpy_procedure.h"
|
||||
|
||||
#include "plpy_elog.h"
|
||||
#include "plpy_main.h"
|
||||
|
||||
|
||||
PLyProcedure *PLy_curr_procedure = NULL;
|
||||
|
||||
|
||||
static HTAB *PLy_procedure_cache = NULL;
|
||||
static HTAB *PLy_trigger_cache = NULL;
|
||||
|
||||
static PLyProcedure *PLy_procedure_create(HeapTuple, Oid, bool);
|
||||
static bool PLy_procedure_argument_valid(PLyTypeInfo *);
|
||||
static bool PLy_procedure_valid(PLyProcedure *, HeapTuple procTup);
|
||||
static char *PLy_procedure_munge_source(const char *, const char *);
|
||||
|
||||
|
||||
void
|
||||
init_procedure_caches(void)
|
||||
{
|
||||
HASHCTL hash_ctl;
|
||||
|
||||
memset(&hash_ctl, 0, sizeof(hash_ctl));
|
||||
hash_ctl.keysize = sizeof(Oid);
|
||||
hash_ctl.entrysize = sizeof(PLyProcedureEntry);
|
||||
hash_ctl.hash = oid_hash;
|
||||
PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
|
||||
HASH_ELEM | HASH_FUNCTION);
|
||||
|
||||
memset(&hash_ctl, 0, sizeof(hash_ctl));
|
||||
hash_ctl.keysize = sizeof(Oid);
|
||||
hash_ctl.entrysize = sizeof(PLyProcedureEntry);
|
||||
hash_ctl.hash = oid_hash;
|
||||
PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl,
|
||||
HASH_ELEM | HASH_FUNCTION);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the name of the last procedure called by the backend (the
|
||||
* innermost, if a plpython procedure call calls the backend and the
|
||||
* backend calls another plpython procedure).
|
||||
*
|
||||
* NB: this returns the SQL name, not the internal Python procedure name
|
||||
*/
|
||||
char *
|
||||
PLy_procedure_name(PLyProcedure *proc)
|
||||
{
|
||||
if (proc == NULL)
|
||||
return "<unknown procedure>";
|
||||
return proc->proname;
|
||||
}
|
||||
|
||||
/*
|
||||
* PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and
|
||||
* returns a new PLyProcedure. fcinfo is the call info, tgreloid is the
|
||||
* relation OID when calling a trigger, or InvalidOid (zero) for ordinary
|
||||
* function calls.
|
||||
*/
|
||||
PLyProcedure *
|
||||
PLy_procedure_get(Oid fn_oid, bool is_trigger)
|
||||
{
|
||||
HeapTuple procTup;
|
||||
PLyProcedureEntry *volatile entry;
|
||||
bool found;
|
||||
|
||||
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
|
||||
if (!HeapTupleIsValid(procTup))
|
||||
elog(ERROR, "cache lookup failed for function %u", fn_oid);
|
||||
|
||||
/* Look for the function in the corresponding cache */
|
||||
if (is_trigger)
|
||||
entry = hash_search(PLy_trigger_cache,
|
||||
&fn_oid, HASH_ENTER, &found);
|
||||
else
|
||||
entry = hash_search(PLy_procedure_cache,
|
||||
&fn_oid, HASH_ENTER, &found);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
if (!found)
|
||||
{
|
||||
/* Haven't found it, create a new cache entry */
|
||||
entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
|
||||
}
|
||||
else if (!PLy_procedure_valid(entry->proc, procTup))
|
||||
{
|
||||
/* Found it, but it's invalid, free and reuse the cache entry */
|
||||
PLy_procedure_delete(entry->proc);
|
||||
PLy_free(entry->proc);
|
||||
entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
|
||||
}
|
||||
/* Found it and it's valid, it's fine to use it */
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
/* Do not leave an uninitialised entry in the cache */
|
||||
if (is_trigger)
|
||||
hash_search(PLy_trigger_cache,
|
||||
&fn_oid, HASH_REMOVE, NULL);
|
||||
else
|
||||
hash_search(PLy_procedure_cache,
|
||||
&fn_oid, HASH_REMOVE, NULL);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
ReleaseSysCache(procTup);
|
||||
|
||||
return entry->proc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new PLyProcedure structure
|
||||
*/
|
||||
static PLyProcedure *
|
||||
PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
|
||||
{
|
||||
char procName[NAMEDATALEN + 256];
|
||||
Form_pg_proc procStruct;
|
||||
PLyProcedure *volatile proc;
|
||||
char *volatile procSource = NULL;
|
||||
Datum prosrcdatum;
|
||||
bool isnull;
|
||||
int i,
|
||||
rv;
|
||||
|
||||
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
|
||||
rv = snprintf(procName, sizeof(procName),
|
||||
"__plpython_procedure_%s_%u",
|
||||
NameStr(procStruct->proname),
|
||||
fn_oid);
|
||||
if (rv >= sizeof(procName) || rv < 0)
|
||||
elog(ERROR, "procedure name would overrun buffer");
|
||||
|
||||
proc = PLy_malloc(sizeof(PLyProcedure));
|
||||
proc->proname = PLy_strdup(NameStr(procStruct->proname));
|
||||
proc->pyname = PLy_strdup(procName);
|
||||
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
|
||||
proc->fn_tid = procTup->t_self;
|
||||
/* Remember if function is STABLE/IMMUTABLE */
|
||||
proc->fn_readonly =
|
||||
(procStruct->provolatile != PROVOLATILE_VOLATILE);
|
||||
PLy_typeinfo_init(&proc->result);
|
||||
for (i = 0; i < FUNC_MAX_ARGS; i++)
|
||||
PLy_typeinfo_init(&proc->args[i]);
|
||||
proc->nargs = 0;
|
||||
proc->code = proc->statics = NULL;
|
||||
proc->globals = NULL;
|
||||
proc->is_setof = procStruct->proretset;
|
||||
proc->setof = NULL;
|
||||
proc->src = NULL;
|
||||
proc->argnames = NULL;
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
/*
|
||||
* get information required for output conversion of the return value,
|
||||
* but only if this isn't a trigger.
|
||||
*/
|
||||
if (!is_trigger)
|
||||
{
|
||||
HeapTuple rvTypeTup;
|
||||
Form_pg_type rvTypeStruct;
|
||||
|
||||
rvTypeTup = SearchSysCache1(TYPEOID,
|
||||
ObjectIdGetDatum(procStruct->prorettype));
|
||||
if (!HeapTupleIsValid(rvTypeTup))
|
||||
elog(ERROR, "cache lookup failed for type %u",
|
||||
procStruct->prorettype);
|
||||
rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
|
||||
|
||||
/* Disallow pseudotype result, except for void or record */
|
||||
if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
|
||||
{
|
||||
if (procStruct->prorettype == TRIGGEROID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("trigger functions can only be called as triggers")));
|
||||
else if (procStruct->prorettype != VOIDOID &&
|
||||
procStruct->prorettype != RECORDOID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("PL/Python functions cannot return type %s",
|
||||
format_type_be(procStruct->prorettype))));
|
||||
}
|
||||
|
||||
if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE ||
|
||||
procStruct->prorettype == RECORDOID)
|
||||
{
|
||||
/*
|
||||
* Tuple: set up later, during first call to
|
||||
* PLy_function_handler
|
||||
*/
|
||||
proc->result.out.d.typoid = procStruct->prorettype;
|
||||
proc->result.out.d.typmod = -1;
|
||||
proc->result.is_rowtype = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* do the real work */
|
||||
PLy_output_datum_func(&proc->result, rvTypeTup);
|
||||
}
|
||||
|
||||
ReleaseSysCache(rvTypeTup);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now get information required for input conversion of the
|
||||
* procedure's arguments. Note that we ignore output arguments here.
|
||||
* If the function returns record, those I/O functions will be set up
|
||||
* when the function is first called.
|
||||
*/
|
||||
if (procStruct->pronargs)
|
||||
{
|
||||
Oid *types;
|
||||
char **names,
|
||||
*modes;
|
||||
int i,
|
||||
pos,
|
||||
total;
|
||||
|
||||
/* extract argument type info from the pg_proc tuple */
|
||||
total = get_func_arg_info(procTup, &types, &names, &modes);
|
||||
|
||||
/* count number of in+inout args into proc->nargs */
|
||||
if (modes == NULL)
|
||||
proc->nargs = total;
|
||||
else
|
||||
{
|
||||
/* proc->nargs was initialized to 0 above */
|
||||
for (i = 0; i < total; i++)
|
||||
{
|
||||
if (modes[i] != PROARGMODE_OUT &&
|
||||
modes[i] != PROARGMODE_TABLE)
|
||||
(proc->nargs)++;
|
||||
}
|
||||
}
|
||||
|
||||
proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs);
|
||||
for (i = pos = 0; i < total; i++)
|
||||
{
|
||||
HeapTuple argTypeTup;
|
||||
Form_pg_type argTypeStruct;
|
||||
|
||||
if (modes &&
|
||||
(modes[i] == PROARGMODE_OUT ||
|
||||
modes[i] == PROARGMODE_TABLE))
|
||||
continue; /* skip OUT arguments */
|
||||
|
||||
Assert(types[i] == procStruct->proargtypes.values[pos]);
|
||||
|
||||
argTypeTup = SearchSysCache1(TYPEOID,
|
||||
ObjectIdGetDatum(types[i]));
|
||||
if (!HeapTupleIsValid(argTypeTup))
|
||||
elog(ERROR, "cache lookup failed for type %u", types[i]);
|
||||
argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
|
||||
|
||||
/* check argument type is OK, set up I/O function info */
|
||||
switch (argTypeStruct->typtype)
|
||||
{
|
||||
case TYPTYPE_PSEUDO:
|
||||
/* Disallow pseudotype argument */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("PL/Python functions cannot accept type %s",
|
||||
format_type_be(types[i]))));
|
||||
break;
|
||||
case TYPTYPE_COMPOSITE:
|
||||
/* we'll set IO funcs at first call */
|
||||
proc->args[pos].is_rowtype = 2;
|
||||
break;
|
||||
default:
|
||||
PLy_input_datum_func(&(proc->args[pos]),
|
||||
types[i],
|
||||
argTypeTup);
|
||||
break;
|
||||
}
|
||||
|
||||
/* get argument name */
|
||||
proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL;
|
||||
|
||||
ReleaseSysCache(argTypeTup);
|
||||
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* get the text of the function.
|
||||
*/
|
||||
prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
|
||||
Anum_pg_proc_prosrc, &isnull);
|
||||
if (isnull)
|
||||
elog(ERROR, "null prosrc");
|
||||
procSource = TextDatumGetCString(prosrcdatum);
|
||||
|
||||
PLy_procedure_compile(proc, procSource);
|
||||
|
||||
pfree(procSource);
|
||||
procSource = NULL;
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
PLy_procedure_delete(proc);
|
||||
if (procSource)
|
||||
pfree(procSource);
|
||||
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert the procedure into the Python interpreter
|
||||
*/
|
||||
void
|
||||
PLy_procedure_compile(PLyProcedure *proc, const char *src)
|
||||
{
|
||||
PyObject *crv = NULL;
|
||||
char *msrc;
|
||||
|
||||
proc->globals = PyDict_Copy(PLy_interp_globals);
|
||||
|
||||
/*
|
||||
* SD is private preserved data between calls. GD is global data shared by
|
||||
* all functions
|
||||
*/
|
||||
proc->statics = PyDict_New();
|
||||
PyDict_SetItemString(proc->globals, "SD", proc->statics);
|
||||
|
||||
/*
|
||||
* insert the function code into the interpreter
|
||||
*/
|
||||
msrc = PLy_procedure_munge_source(proc->pyname, src);
|
||||
/* Save the mangled source for later inclusion in tracebacks */
|
||||
proc->src = PLy_strdup(msrc);
|
||||
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
|
||||
pfree(msrc);
|
||||
|
||||
if (crv != NULL)
|
||||
{
|
||||
int clen;
|
||||
char call[NAMEDATALEN + 256];
|
||||
|
||||
Py_DECREF(crv);
|
||||
|
||||
/*
|
||||
* compile a call to the function
|
||||
*/
|
||||
clen = snprintf(call, sizeof(call), "%s()", proc->pyname);
|
||||
if (clen < 0 || clen >= sizeof(call))
|
||||
elog(ERROR, "string would overflow buffer");
|
||||
proc->code = Py_CompileString(call, "<string>", Py_eval_input);
|
||||
if (proc->code != NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
if (proc->proname)
|
||||
PLy_elog(ERROR, "could not compile PL/Python function \"%s\"",
|
||||
proc->proname);
|
||||
else
|
||||
PLy_elog(ERROR, "could not compile anonymous PL/Python code block");
|
||||
}
|
||||
|
||||
void
|
||||
PLy_procedure_delete(PLyProcedure *proc)
|
||||
{
|
||||
int i;
|
||||
|
||||
Py_XDECREF(proc->code);
|
||||
Py_XDECREF(proc->statics);
|
||||
Py_XDECREF(proc->globals);
|
||||
if (proc->proname)
|
||||
PLy_free(proc->proname);
|
||||
if (proc->pyname)
|
||||
PLy_free(proc->pyname);
|
||||
for (i = 0; i < proc->nargs; i++)
|
||||
{
|
||||
if (proc->args[i].is_rowtype == 1)
|
||||
{
|
||||
if (proc->args[i].in.r.atts)
|
||||
PLy_free(proc->args[i].in.r.atts);
|
||||
if (proc->args[i].out.r.atts)
|
||||
PLy_free(proc->args[i].out.r.atts);
|
||||
}
|
||||
if (proc->argnames && proc->argnames[i])
|
||||
PLy_free(proc->argnames[i]);
|
||||
}
|
||||
if (proc->src)
|
||||
PLy_free(proc->src);
|
||||
if (proc->argnames)
|
||||
PLy_free(proc->argnames);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if our cached information about a datatype is still valid
|
||||
*/
|
||||
static bool
|
||||
PLy_procedure_argument_valid(PLyTypeInfo *arg)
|
||||
{
|
||||
HeapTuple relTup;
|
||||
bool valid;
|
||||
|
||||
/* Nothing to cache unless type is composite */
|
||||
if (arg->is_rowtype != 1)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Zero typ_relid means that we got called on an output argument of a
|
||||
* function returning a unnamed record type; the info for it can't change.
|
||||
*/
|
||||
if (!OidIsValid(arg->typ_relid))
|
||||
return true;
|
||||
|
||||
/* Else we should have some cached data */
|
||||
Assert(TransactionIdIsValid(arg->typrel_xmin));
|
||||
Assert(ItemPointerIsValid(&arg->typrel_tid));
|
||||
|
||||
/* Get the pg_class tuple for the data type */
|
||||
relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid));
|
||||
if (!HeapTupleIsValid(relTup))
|
||||
elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid);
|
||||
|
||||
/* If it has changed, the cached data is not valid */
|
||||
valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) &&
|
||||
ItemPointerEquals(&arg->typrel_tid, &relTup->t_self));
|
||||
|
||||
ReleaseSysCache(relTup);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decide whether a cached PLyProcedure struct is still valid
|
||||
*/
|
||||
static bool
|
||||
PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
|
||||
{
|
||||
int i;
|
||||
bool valid;
|
||||
|
||||
Assert(proc != NULL);
|
||||
|
||||
/* If the pg_proc tuple has changed, it's not valid */
|
||||
if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
|
||||
ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
|
||||
return false;
|
||||
|
||||
/* Else check the input argument datatypes */
|
||||
valid = true;
|
||||
for (i = 0; i < proc->nargs; i++)
|
||||
{
|
||||
valid = PLy_procedure_argument_valid(&proc->args[i]);
|
||||
|
||||
/* Short-circuit on first changed argument */
|
||||
if (!valid)
|
||||
break;
|
||||
}
|
||||
|
||||
/* if the output type is composite, it might have changed */
|
||||
if (valid)
|
||||
valid = PLy_procedure_argument_valid(&proc->result);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static char *
|
||||
PLy_procedure_munge_source(const char *name, const char *src)
|
||||
{
|
||||
char *mrc,
|
||||
*mp;
|
||||
const char *sp;
|
||||
size_t mlen,
|
||||
plen;
|
||||
|
||||
/*
|
||||
* room for function source and the def statement
|
||||
*/
|
||||
mlen = (strlen(src) * 2) + strlen(name) + 16;
|
||||
|
||||
mrc = palloc(mlen);
|
||||
plen = snprintf(mrc, mlen, "def %s():\n\t", name);
|
||||
Assert(plen >= 0 && plen < mlen);
|
||||
|
||||
sp = src;
|
||||
mp = mrc + plen;
|
||||
|
||||
while (*sp != '\0')
|
||||
{
|
||||
if (*sp == '\r' && *(sp + 1) == '\n')
|
||||
sp++;
|
||||
|
||||
if (*sp == '\n' || *sp == '\r')
|
||||
{
|
||||
*mp++ = '\n';
|
||||
*mp++ = '\t';
|
||||
sp++;
|
||||
}
|
||||
else
|
||||
*mp++ = *sp++;
|
||||
}
|
||||
*mp++ = '\n';
|
||||
*mp++ = '\n';
|
||||
*mp = '\0';
|
||||
|
||||
if (mp > (mrc + mlen))
|
||||
elog(FATAL, "buffer overrun in PLy_munge_source");
|
||||
|
||||
return mrc;
|
||||
}
|
52
src/pl/plpython/plpy_procedure.h
Normal file
52
src/pl/plpython/plpy_procedure.h
Normal file
@ -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 */
|
180
src/pl/plpython/plpy_resultobject.c
Normal file
180
src/pl/plpython/plpy_resultobject.c
Normal file
@ -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;
|
||||
}
|
20
src/pl/plpython/plpy_resultobject.h
Normal file
20
src/pl/plpython/plpy_resultobject.h
Normal file
@ -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 */
|
559
src/pl/plpython/plpy_spi.c
Normal file
559
src/pl/plpython/plpy_spi.c
Normal file
@ -0,0 +1,559 @@
|
||||
/*
|
||||
* interface to SPI functions
|
||||
*
|
||||
* src/pl/plpython/plpy_spi.c
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "executor/spi_priv.h"
|
||||
#include "mb/pg_wchar.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "plpython.h"
|
||||
|
||||
#include "plpy_spi.h"
|
||||
|
||||
#include "plpy_elog.h"
|
||||
#include "plpy_planobject.h"
|
||||
#include "plpy_plpymodule.h"
|
||||
#include "plpy_procedure.h"
|
||||
#include "plpy_resultobject.h"
|
||||
|
||||
|
||||
static PyObject *PLy_spi_execute_query(char *, long );
|
||||
static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long);
|
||||
static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int);
|
||||
static void PLy_spi_exception_set(PyObject *, ErrorData *);
|
||||
|
||||
|
||||
/* prepare(query="select * from foo")
|
||||
* prepare(query="select * from foo where bar = $1", params=["text"])
|
||||
* prepare(query="select * from foo where bar = $1", params=["text"], limit=5)
|
||||
*/
|
||||
PyObject *
|
||||
PLy_spi_prepare(PyObject *self, PyObject *args)
|
||||
{
|
||||
PLyPlanObject *plan;
|
||||
PyObject *list = NULL;
|
||||
PyObject *volatile optr = NULL;
|
||||
char *query;
|
||||
volatile MemoryContext oldcontext;
|
||||
volatile ResourceOwner oldowner;
|
||||
volatile int nargs;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
|
||||
return NULL;
|
||||
|
||||
if (list && (!PySequence_Check(list)))
|
||||
{
|
||||
PLy_exception_set(PyExc_TypeError,
|
||||
"second argument of plpy.prepare must be a sequence");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
|
||||
return NULL;
|
||||
|
||||
nargs = list ? PySequence_Length(list) : 0;
|
||||
|
||||
plan->nargs = nargs;
|
||||
plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL;
|
||||
plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL;
|
||||
plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL;
|
||||
|
||||
oldcontext = CurrentMemoryContext;
|
||||
oldowner = CurrentResourceOwner;
|
||||
|
||||
PLy_spi_subtransaction_begin(oldcontext, oldowner);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* the other loop might throw an exception, if PLyTypeInfo member
|
||||
* isn't properly initialized the Py_DECREF(plan) will go boom
|
||||
*/
|
||||
for (i = 0; i < nargs; i++)
|
||||
{
|
||||
PLy_typeinfo_init(&plan->args[i]);
|
||||
plan->values[i] = PointerGetDatum(NULL);
|
||||
}
|
||||
|
||||
for (i = 0; i < nargs; i++)
|
||||
{
|
||||
char *sptr;
|
||||
HeapTuple typeTup;
|
||||
Oid typeId;
|
||||
int32 typmod;
|
||||
Form_pg_type typeStruct;
|
||||
|
||||
optr = PySequence_GetItem(list, i);
|
||||
if (PyString_Check(optr))
|
||||
sptr = PyString_AsString(optr);
|
||||
else if (PyUnicode_Check(optr))
|
||||
sptr = PLyUnicode_AsString(optr);
|
||||
else
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errmsg("plpy.prepare: type name at ordinal position %d is not a string", i)));
|
||||
sptr = NULL; /* keep compiler quiet */
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Resolve argument type names and then look them up by
|
||||
* oid in the system cache, and remember the required
|
||||
*information for input conversion.
|
||||
********************************************************/
|
||||
|
||||
parseTypeString(sptr, &typeId, &typmod);
|
||||
|
||||
typeTup = SearchSysCache1(TYPEOID,
|
||||
ObjectIdGetDatum(typeId));
|
||||
if (!HeapTupleIsValid(typeTup))
|
||||
elog(ERROR, "cache lookup failed for type %u", typeId);
|
||||
|
||||
Py_DECREF(optr);
|
||||
|
||||
/*
|
||||
* set optr to NULL, so we won't try to unref it again in case of
|
||||
* an error
|
||||
*/
|
||||
optr = NULL;
|
||||
|
||||
plan->types[i] = typeId;
|
||||
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
|
||||
if (typeStruct->typtype != TYPTYPE_COMPOSITE)
|
||||
PLy_output_datum_func(&plan->args[i], typeTup);
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("plpy.prepare does not support composite types")));
|
||||
ReleaseSysCache(typeTup);
|
||||
}
|
||||
|
||||
pg_verifymbstr(query, strlen(query), false);
|
||||
plan->plan = SPI_prepare(query, plan->nargs, plan->types);
|
||||
if (plan->plan == NULL)
|
||||
elog(ERROR, "SPI_prepare failed: %s",
|
||||
SPI_result_code_string(SPI_result));
|
||||
|
||||
/* transfer plan from procCxt to topCxt */
|
||||
if (SPI_keepplan(plan->plan))
|
||||
elog(ERROR, "SPI_keepplan failed");
|
||||
|
||||
PLy_spi_subtransaction_commit(oldcontext, oldowner);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
Py_DECREF(plan);
|
||||
Py_XDECREF(optr);
|
||||
|
||||
PLy_spi_subtransaction_abort(oldcontext, oldowner);
|
||||
return NULL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
Assert(plan->plan != NULL);
|
||||
return (PyObject *) plan;
|
||||
}
|
||||
|
||||
/* execute(query="select * from foo", limit=5)
|
||||
* execute(plan=plan, values=(foo, bar), limit=5)
|
||||
*/
|
||||
PyObject *
|
||||
PLy_spi_execute(PyObject *self, PyObject *args)
|
||||
{
|
||||
char *query;
|
||||
PyObject *plan;
|
||||
PyObject *list = NULL;
|
||||
long limit = 0;
|
||||
|
||||
if (PyArg_ParseTuple(args, "s|l", &query, &limit))
|
||||
return PLy_spi_execute_query(query, limit);
|
||||
|
||||
PyErr_Clear();
|
||||
|
||||
if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) &&
|
||||
is_PLyPlanObject(plan))
|
||||
return PLy_spi_execute_plan(plan, list, limit);
|
||||
|
||||
PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
|
||||
{
|
||||
volatile int nargs;
|
||||
int i,
|
||||
rv;
|
||||
PLyPlanObject *plan;
|
||||
volatile MemoryContext oldcontext;
|
||||
volatile ResourceOwner oldowner;
|
||||
PyObject *ret;
|
||||
|
||||
if (list != NULL)
|
||||
{
|
||||
if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list))
|
||||
{
|
||||
PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument");
|
||||
return NULL;
|
||||
}
|
||||
nargs = PySequence_Length(list);
|
||||
}
|
||||
else
|
||||
nargs = 0;
|
||||
|
||||
plan = (PLyPlanObject *) ob;
|
||||
|
||||
if (nargs != plan->nargs)
|
||||
{
|
||||
char *sv;
|
||||
PyObject *so = PyObject_Str(list);
|
||||
|
||||
if (!so)
|
||||
PLy_elog(ERROR, "could not execute plan");
|
||||
sv = PyString_AsString(so);
|
||||
PLy_exception_set_plural(PyExc_TypeError,
|
||||
"Expected sequence of %d argument, got %d: %s",
|
||||
"Expected sequence of %d arguments, got %d: %s",
|
||||
plan->nargs,
|
||||
plan->nargs, nargs, sv);
|
||||
Py_DECREF(so);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
oldcontext = CurrentMemoryContext;
|
||||
oldowner = CurrentResourceOwner;
|
||||
|
||||
PLy_spi_subtransaction_begin(oldcontext, oldowner);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
char *volatile nulls;
|
||||
volatile int j;
|
||||
|
||||
if (nargs > 0)
|
||||
nulls = palloc(nargs * sizeof(char));
|
||||
else
|
||||
nulls = NULL;
|
||||
|
||||
for (j = 0; j < nargs; j++)
|
||||
{
|
||||
PyObject *elem;
|
||||
|
||||
elem = PySequence_GetItem(list, j);
|
||||
if (elem != Py_None)
|
||||
{
|
||||
PG_TRY();
|
||||
{
|
||||
plan->values[j] =
|
||||
plan->args[j].out.d.func(&(plan->args[j].out.d),
|
||||
-1,
|
||||
elem);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
Py_DECREF(elem);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
Py_DECREF(elem);
|
||||
nulls[j] = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
Py_DECREF(elem);
|
||||
plan->values[j] =
|
||||
InputFunctionCall(&(plan->args[j].out.d.typfunc),
|
||||
NULL,
|
||||
plan->args[j].out.d.typioparam,
|
||||
-1);
|
||||
nulls[j] = 'n';
|
||||
}
|
||||
}
|
||||
|
||||
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
|
||||
PLy_curr_procedure->fn_readonly, limit);
|
||||
ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
|
||||
|
||||
if (nargs > 0)
|
||||
pfree(nulls);
|
||||
|
||||
PLy_spi_subtransaction_commit(oldcontext, oldowner);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
int k;
|
||||
|
||||
/*
|
||||
* cleanup plan->values array
|
||||
*/
|
||||
for (k = 0; k < nargs; k++)
|
||||
{
|
||||
if (!plan->args[k].out.d.typbyval &&
|
||||
(plan->values[k] != PointerGetDatum(NULL)))
|
||||
{
|
||||
pfree(DatumGetPointer(plan->values[k]));
|
||||
plan->values[k] = PointerGetDatum(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
PLy_spi_subtransaction_abort(oldcontext, oldowner);
|
||||
return NULL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
for (i = 0; i < nargs; i++)
|
||||
{
|
||||
if (!plan->args[i].out.d.typbyval &&
|
||||
(plan->values[i] != PointerGetDatum(NULL)))
|
||||
{
|
||||
pfree(DatumGetPointer(plan->values[i]));
|
||||
plan->values[i] = PointerGetDatum(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (rv < 0)
|
||||
{
|
||||
PLy_exception_set(PLy_exc_spi_error,
|
||||
"SPI_execute_plan failed: %s",
|
||||
SPI_result_code_string(rv));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PLy_spi_execute_query(char *query, long limit)
|
||||
{
|
||||
int rv;
|
||||
volatile MemoryContext oldcontext;
|
||||
volatile ResourceOwner oldowner;
|
||||
PyObject *ret;
|
||||
|
||||
oldcontext = CurrentMemoryContext;
|
||||
oldowner = CurrentResourceOwner;
|
||||
|
||||
PLy_spi_subtransaction_begin(oldcontext, oldowner);
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
pg_verifymbstr(query, strlen(query), false);
|
||||
rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
|
||||
ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
|
||||
|
||||
PLy_spi_subtransaction_commit(oldcontext, oldowner);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
PLy_spi_subtransaction_abort(oldcontext, oldowner);
|
||||
return NULL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
if (rv < 0)
|
||||
{
|
||||
PLy_exception_set(PLy_exc_spi_error,
|
||||
"SPI_execute failed: %s",
|
||||
SPI_result_code_string(rv));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
|
||||
{
|
||||
PLyResultObject *result;
|
||||
volatile MemoryContext oldcontext;
|
||||
|
||||
result = (PLyResultObject *) PLy_result_new();
|
||||
Py_DECREF(result->status);
|
||||
result->status = PyInt_FromLong(status);
|
||||
|
||||
if (status > 0 && tuptable == NULL)
|
||||
{
|
||||
Py_DECREF(result->nrows);
|
||||
result->nrows = PyInt_FromLong(rows);
|
||||
}
|
||||
else if (status > 0 && tuptable != NULL)
|
||||
{
|
||||
PLyTypeInfo args;
|
||||
int i;
|
||||
|
||||
Py_DECREF(result->nrows);
|
||||
result->nrows = PyInt_FromLong(rows);
|
||||
PLy_typeinfo_init(&args);
|
||||
|
||||
oldcontext = CurrentMemoryContext;
|
||||
PG_TRY();
|
||||
{
|
||||
if (rows)
|
||||
{
|
||||
Py_DECREF(result->rows);
|
||||
result->rows = PyList_New(rows);
|
||||
|
||||
PLy_input_tuple_funcs(&args, tuptable->tupdesc);
|
||||
for (i = 0; i < rows; i++)
|
||||
{
|
||||
PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i],
|
||||
tuptable->tupdesc);
|
||||
|
||||
PyList_SetItem(result->rows, i, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
if (!PyErr_Occurred())
|
||||
PLy_exception_set(PLy_exc_error,
|
||||
"unrecognized error in PLy_spi_execute_fetch_result");
|
||||
PLy_typeinfo_dealloc(&args);
|
||||
SPI_freetuptable(tuptable);
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
PLy_typeinfo_dealloc(&args);
|
||||
SPI_freetuptable(tuptable);
|
||||
}
|
||||
|
||||
return (PyObject *) result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utilities for running SPI functions in subtransactions.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* MemoryContext oldcontext = CurrentMemoryContext;
|
||||
* ResourceOwner oldowner = CurrentResourceOwner;
|
||||
*
|
||||
* PLy_spi_subtransaction_begin(oldcontext, oldowner);
|
||||
* PG_TRY();
|
||||
* {
|
||||
* <call SPI functions>
|
||||
* PLy_spi_subtransaction_commit(oldcontext, oldowner);
|
||||
* }
|
||||
* PG_CATCH();
|
||||
* {
|
||||
* <do cleanup>
|
||||
* PLy_spi_subtransaction_abort(oldcontext, oldowner);
|
||||
* return NULL;
|
||||
* }
|
||||
* PG_END_TRY();
|
||||
*
|
||||
* These utilities take care of restoring connection to the SPI manager and
|
||||
* setting a Python exception in case of an abort.
|
||||
*/
|
||||
void
|
||||
PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner)
|
||||
{
|
||||
BeginInternalSubTransaction(NULL);
|
||||
/* Want to run inside function's memory context */
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
void
|
||||
PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner)
|
||||
{
|
||||
/* Commit the inner transaction, return to outer xact context */
|
||||
ReleaseCurrentSubTransaction();
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
CurrentResourceOwner = oldowner;
|
||||
|
||||
/*
|
||||
* AtEOSubXact_SPI() should not have popped any SPI context, but just
|
||||
* in case it did, make sure we remain connected.
|
||||
*/
|
||||
SPI_restore_connection();
|
||||
}
|
||||
|
||||
void
|
||||
PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner)
|
||||
{
|
||||
ErrorData *edata;
|
||||
PLyExceptionEntry *entry;
|
||||
PyObject *exc;
|
||||
|
||||
/* Save error info */
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
edata = CopyErrorData();
|
||||
FlushErrorState();
|
||||
|
||||
/* Abort the inner transaction */
|
||||
RollbackAndReleaseCurrentSubTransaction();
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
CurrentResourceOwner = oldowner;
|
||||
|
||||
/*
|
||||
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it will have
|
||||
* left us in a disconnected state. We need this hack to return to
|
||||
* connected state.
|
||||
*/
|
||||
SPI_restore_connection();
|
||||
|
||||
/* Look up the correct exception */
|
||||
entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
|
||||
HASH_FIND, NULL);
|
||||
/* We really should find it, but just in case have a fallback */
|
||||
Assert(entry != NULL);
|
||||
exc = entry ? entry->exc : PLy_exc_spi_error;
|
||||
/* Make Python raise the exception */
|
||||
PLy_spi_exception_set(exc, edata);
|
||||
FreeErrorData(edata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Raise a SPIError, passing in it more error details, like the
|
||||
* internal query and error position.
|
||||
*/
|
||||
static void
|
||||
PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
|
||||
{
|
||||
PyObject *args = NULL;
|
||||
PyObject *spierror = NULL;
|
||||
PyObject *spidata = NULL;
|
||||
|
||||
args = Py_BuildValue("(s)", edata->message);
|
||||
if (!args)
|
||||
goto failure;
|
||||
|
||||
/* create a new SPI exception with the error message as the parameter */
|
||||
spierror = PyObject_CallObject(excclass, args);
|
||||
if (!spierror)
|
||||
goto failure;
|
||||
|
||||
spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint,
|
||||
edata->internalquery, edata->internalpos);
|
||||
if (!spidata)
|
||||
goto failure;
|
||||
|
||||
if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
|
||||
goto failure;
|
||||
|
||||
PyErr_SetObject(excclass, spierror);
|
||||
|
||||
Py_DECREF(args);
|
||||
Py_DECREF(spierror);
|
||||
Py_DECREF(spidata);
|
||||
return;
|
||||
|
||||
failure:
|
||||
Py_XDECREF(args);
|
||||
Py_XDECREF(spierror);
|
||||
Py_XDECREF(spidata);
|
||||
elog(ERROR, "could not convert SPI error to Python exception");
|
||||
}
|
25
src/pl/plpython/plpy_spi.h
Normal file
25
src/pl/plpython/plpy_spi.h
Normal file
@ -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 */
|
217
src/pl/plpython/plpy_subxactobject.c
Normal file
217
src/pl/plpython/plpy_subxactobject.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* the PLySubtransaction class
|
||||
*
|
||||
* src/pl/plpython/plpy_subxactobject.c
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "executor/spi.h"
|
||||
|
||||
#include "plpython.h"
|
||||
|
||||
#include "plpy_subxactobject.h"
|
||||
|
||||
#include "plpy_elog.h"
|
||||
|
||||
|
||||
List *explicit_subtransactions = NIL;
|
||||
|
||||
|
||||
static void PLy_subtransaction_dealloc(PyObject *);
|
||||
static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *);
|
||||
static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *);
|
||||
|
||||
static char PLy_subtransaction_doc[] = {
|
||||
"PostgreSQL subtransaction context manager"
|
||||
};
|
||||
|
||||
static PyMethodDef PLy_subtransaction_methods[] = {
|
||||
{"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL},
|
||||
{"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL},
|
||||
/* user-friendly names for Python <2.6 */
|
||||
{"enter", PLy_subtransaction_enter, METH_VARARGS, NULL},
|
||||
{"exit", PLy_subtransaction_exit, METH_VARARGS, NULL},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static PyTypeObject PLy_SubtransactionType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"PLySubtransaction", /* tp_name */
|
||||
sizeof(PLySubtransactionObject), /* tp_size */
|
||||
0, /* tp_itemsize */
|
||||
|
||||
/*
|
||||
* methods
|
||||
*/
|
||||
PLy_subtransaction_dealloc, /* tp_dealloc */
|
||||
0, /* tp_print */
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
0, /* tp_compare */
|
||||
0, /* tp_repr */
|
||||
0, /* tp_as_number */
|
||||
0, /* tp_as_sequence */
|
||||
0, /* tp_as_mapping */
|
||||
0, /* tp_hash */
|
||||
0, /* tp_call */
|
||||
0, /* tp_str */
|
||||
0, /* tp_getattro */
|
||||
0, /* tp_setattro */
|
||||
0, /* tp_as_buffer */
|
||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||
PLy_subtransaction_doc, /* tp_doc */
|
||||
0, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
PLy_subtransaction_methods, /* tp_tpmethods */
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
PLy_subtransaction_init_type(void)
|
||||
{
|
||||
if (PyType_Ready(&PLy_SubtransactionType) < 0)
|
||||
elog(ERROR, "could not initialize PLy_SubtransactionType");
|
||||
}
|
||||
|
||||
/* s = plpy.subtransaction() */
|
||||
PyObject *
|
||||
PLy_subtransaction_new(PyObject *self, PyObject *unused)
|
||||
{
|
||||
PLySubtransactionObject *ob;
|
||||
|
||||
ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType);
|
||||
|
||||
if (ob == NULL)
|
||||
return NULL;
|
||||
|
||||
ob->started = false;
|
||||
ob->exited = false;
|
||||
|
||||
return (PyObject *) ob;
|
||||
}
|
||||
|
||||
/* Python requires a dealloc function to be defined */
|
||||
static void
|
||||
PLy_subtransaction_dealloc(PyObject *subxact)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* subxact.__enter__() or subxact.enter()
|
||||
*
|
||||
* Start an explicit subtransaction. SPI calls within an explicit
|
||||
* subtransaction will not start another one, so you can atomically
|
||||
* execute many SPI calls and still get a controllable exception if
|
||||
* one of them fails.
|
||||
*/
|
||||
static PyObject *
|
||||
PLy_subtransaction_enter(PyObject *self, PyObject *unused)
|
||||
{
|
||||
PLySubtransactionData *subxactdata;
|
||||
MemoryContext oldcontext;
|
||||
PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
|
||||
|
||||
if (subxact->started)
|
||||
{
|
||||
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (subxact->exited)
|
||||
{
|
||||
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
subxact->started = true;
|
||||
oldcontext = CurrentMemoryContext;
|
||||
|
||||
subxactdata = PLy_malloc(sizeof(*subxactdata));
|
||||
subxactdata->oldcontext = oldcontext;
|
||||
subxactdata->oldowner = CurrentResourceOwner;
|
||||
|
||||
BeginInternalSubTransaction(NULL);
|
||||
/* Do not want to leave the previous memory context */
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
|
||||
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
* subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb)
|
||||
*
|
||||
* Exit an explicit subtransaction. exc_type is an exception type, exc
|
||||
* is the exception object, tb is the traceback. If exc_type is None,
|
||||
* commit the subtransactiony, if not abort it.
|
||||
*
|
||||
* The method signature is chosen to allow subtransaction objects to
|
||||
* be used as context managers as described in
|
||||
* <http://www.python.org/dev/peps/pep-0343/>.
|
||||
*/
|
||||
static PyObject *
|
||||
PLy_subtransaction_exit(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *type;
|
||||
PyObject *value;
|
||||
PyObject *traceback;
|
||||
PLySubtransactionData *subxactdata;
|
||||
PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback))
|
||||
return NULL;
|
||||
|
||||
if (!subxact->started)
|
||||
{
|
||||
PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (subxact->exited)
|
||||
{
|
||||
PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (explicit_subtransactions == NIL)
|
||||
{
|
||||
PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
subxact->exited = true;
|
||||
|
||||
if (type != Py_None)
|
||||
{
|
||||
/* Abort the inner transaction */
|
||||
RollbackAndReleaseCurrentSubTransaction();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseCurrentSubTransaction();
|
||||
}
|
||||
|
||||
subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions);
|
||||
explicit_subtransactions = list_delete_first(explicit_subtransactions);
|
||||
|
||||
MemoryContextSwitchTo(subxactdata->oldcontext);
|
||||
CurrentResourceOwner = subxactdata->oldowner;
|
||||
PLy_free(subxactdata);
|
||||
|
||||
/*
|
||||
* AtEOSubXact_SPI() should not have popped any SPI context, but just in
|
||||
* case it did, make sure we remain connected.
|
||||
*/
|
||||
SPI_restore_connection();
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
29
src/pl/plpython/plpy_subxactobject.h
Normal file
29
src/pl/plpython/plpy_subxactobject.h
Normal file
@ -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 */
|
1038
src/pl/plpython/plpy_typeio.c
Normal file
1038
src/pl/plpython/plpy_typeio.c
Normal file
File diff suppressed because it is too large
Load Diff
107
src/pl/plpython/plpy_typeio.h
Normal file
107
src/pl/plpython/plpy_typeio.h
Normal file
@ -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 */
|
125
src/pl/plpython/plpy_util.c
Normal file
125
src/pl/plpython/plpy_util.c
Normal file
@ -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 */
|
21
src/pl/plpython/plpy_util.h
Normal file
21
src/pl/plpython/plpy_util.h
Normal file
@ -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 */
|
File diff suppressed because it is too large
Load Diff
156
src/pl/plpython/plpython.h
Normal file
156
src/pl/plpython/plpython.h
Normal file
@ -0,0 +1,156 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* plpython.h - Python as a procedural language for PostgreSQL
|
||||
*
|
||||
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* src/pl/plpython/plpython.h
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef PLPYTHON_H
|
||||
#define PLPYTHON_H
|
||||
|
||||
/*
|
||||
* Include order should be: postgres.h, other postgres headers, plpython.h,
|
||||
* other plpython headers
|
||||
*/
|
||||
#ifndef POSTGRES_H
|
||||
#error postgres.h must be included before plpython.h
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Undefine some things that get (re)defined in the Python headers. They aren't
|
||||
* used by the PL/Python code, and all PostgreSQL headers should be included
|
||||
* earlier, so this should be pretty safe.
|
||||
*/
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
#undef HAVE_STRERROR
|
||||
#undef HAVE_TZNAME
|
||||
|
||||
/*
|
||||
* Sometimes python carefully scribbles on our *printf macros.
|
||||
* So we undefine them here and redefine them after it's done its dirty deed.
|
||||
*/
|
||||
|
||||
#ifdef USE_REPL_SNPRINTF
|
||||
#undef snprintf
|
||||
#undef vsnprintf
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
/* Python uses #pragma to bring in a non-default libpython on VC++ if
|
||||
* _DEBUG is defined */
|
||||
#undef _DEBUG
|
||||
/* Also hide away errcode, since we load Python.h before postgres.h */
|
||||
#define errcode __msvc_errcode
|
||||
#include <Python.h>
|
||||
#undef errcode
|
||||
#define _DEBUG
|
||||
#elif defined (_MSC_VER)
|
||||
#define errcode __msvc_errcode
|
||||
#include <Python.h>
|
||||
#undef errcode
|
||||
#else
|
||||
#include <Python.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Py_ssize_t compat for Python <= 2.4
|
||||
*/
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
#define PY_SSIZE_T_MIN INT_MIN
|
||||
#endif
|
||||
|
||||
/*
|
||||
* PyBool_FromLong is supported from 2.3.
|
||||
*/
|
||||
#if PY_VERSION_HEX < 0x02030000
|
||||
#define PyBool_FromLong(x) PyInt_FromLong(x)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Python 2/3 strings/unicode/bytes handling. Python 2 has strings
|
||||
* and unicode, Python 3 has strings, which are unicode on the C
|
||||
* level, and bytes. The porting convention, which is similarly used
|
||||
* in Python 2.6, is that "Unicode" is always unicode, and "Bytes" are
|
||||
* bytes in Python 3 and strings in Python 2. Since we keep
|
||||
* supporting Python 2 and its usual strings, we provide a
|
||||
* compatibility layer for Python 3 that when asked to convert a C
|
||||
* string to a Python string it converts the C string from the
|
||||
* PostgreSQL server encoding to a Python Unicode object.
|
||||
*/
|
||||
|
||||
#if PY_VERSION_HEX < 0x02060000
|
||||
/* This is exactly the compatibility layer that Python 2.6 uses. */
|
||||
#define PyBytes_AsString PyString_AsString
|
||||
#define PyBytes_FromStringAndSize PyString_FromStringAndSize
|
||||
#define PyBytes_Size PyString_Size
|
||||
#define PyObject_Bytes PyObject_Str
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define PyString_Check(x) 0
|
||||
#define PyString_AsString(x) PLyUnicode_AsString(x)
|
||||
#define PyString_FromString(x) PLyUnicode_FromString(x)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Python 3 only has long.
|
||||
*/
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define PyInt_FromLong(x) PyLong_FromLong(x)
|
||||
#define PyInt_AsLong(x) PyLong_AsLong(x)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* PyVarObject_HEAD_INIT was added in Python 2.6. Its use is
|
||||
* necessary to handle both Python 2 and 3. This replacement
|
||||
* definition is for Python <=2.5
|
||||
*/
|
||||
#ifndef PyVarObject_HEAD_INIT
|
||||
#define PyVarObject_HEAD_INIT(type, size) \
|
||||
PyObject_HEAD_INIT(type) size,
|
||||
#endif
|
||||
|
||||
/* Python 3 removed the Py_TPFLAGS_HAVE_ITER flag */
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define Py_TPFLAGS_HAVE_ITER 0
|
||||
#endif
|
||||
|
||||
/* define our text domain for translations */
|
||||
#undef TEXTDOMAIN
|
||||
#define TEXTDOMAIN PG_TEXTDOMAIN("plpython")
|
||||
|
||||
#include <compile.h>
|
||||
#include <eval.h>
|
||||
|
||||
/* put back our snprintf and vsnprintf */
|
||||
#ifdef USE_REPL_SNPRINTF
|
||||
#ifdef snprintf
|
||||
#undef snprintf
|
||||
#endif
|
||||
#ifdef vsnprintf
|
||||
#undef vsnprintf
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#define vsnprintf(...) pg_vsnprintf(__VA_ARGS__)
|
||||
#define snprintf(...) pg_snprintf(__VA_ARGS__)
|
||||
#else
|
||||
#define vsnprintf pg_vsnprintf
|
||||
#define snprintf pg_snprintf
|
||||
#endif /* __GNUC__ */
|
||||
#endif /* USE_REPL_SNPRINTF */
|
||||
|
||||
/*
|
||||
* Used throughout, and also by the Python 2/3 porting layer, so it's easier to
|
||||
* just include it everywhere.
|
||||
*/
|
||||
#include "plpy_util.h"
|
||||
|
||||
#endif /* PLPYTHON_H */
|
Loading…
Reference in New Issue
Block a user