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