mirror of https://github.com/postgres/postgres
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 Eisentrautpull/1/head
parent
59e242a496
commit
147c248254
@ -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; |
||||||
|
} |
@ -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 */ |
@ -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); |
||||||
|
} |
@ -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 */ |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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 */ |
@ -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"); |
||||||
|
} |
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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 */ |
@ -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"); |
||||||
|
} |
@ -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 */ |
@ -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; |
||||||
|
} |
@ -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 */ |
File diff suppressed because it is too large
Load Diff
@ -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 */ |
@ -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 */ |
@ -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
@ -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