#include "parser/parse_type.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+#include "utils/hsearch.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global scope */
- PyObject *me; /* PyCObject containing pointer to this
- * PLyProcedure */
} PLyProcedure;
+/* the procedure cache entry */
+typedef struct PLyProcedureEntry
+{
+ Oid fn_oid; /* hash key */
+ PLyProcedure *proc;
+} PLyProcedureEntry;
+
+
/* Python objects */
typedef struct PLyPlanObject
{
static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *);
-static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo,
- Oid tgreloid);
+static PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger);
-static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid tgreloid,
- char *key);
+static PLyProcedure *PLy_procedure_create(HeapTuple procTup,
+ Oid fn_oid, bool is_trigger);
static void PLy_procedure_compile(PLyProcedure *, const char *);
static char *PLy_procedure_munge_source(const char *, const char *);
static PyObject *PLy_interp_globals = NULL;
static PyObject *PLy_interp_safe_globals = NULL;
-static PyObject *PLy_procedure_cache = NULL;
+static HTAB *PLy_procedure_cache = NULL;
+static HTAB *PLy_trigger_cache = NULL;
/* Python exceptions */
static PyObject *PLy_exc_error = NULL;
{
Datum retval;
PLyProcedure *save_curr_proc;
- PLyProcedure *volatile proc = NULL;
ErrorContextCallback plerrcontext;
if (SPI_connect() != SPI_OK_CONNECT)
PG_TRY();
{
+ PLyProcedure *proc;
+
if (CALLED_AS_TRIGGER(fcinfo))
{
- TriggerData *tdata = (TriggerData *) fcinfo->context;
HeapTuple trv;
- proc = PLy_procedure_get(fcinfo,
- RelationGetRelid(tdata->tg_relation));
+ proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true);
PLy_curr_procedure = proc;
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
- proc = PLy_procedure_get(fcinfo, InvalidOid);
+ proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
PLy_curr_procedure = proc;
retval = PLy_function_handler(fcinfo, proc);
}
PG_CATCH();
{
PLy_curr_procedure = save_curr_proc;
- if (proc)
- {
- /* note: Py_DECREF needs braces around it, as of 2003/08 */
- Py_DECREF(proc->me);
- }
PyErr_Clear();
PG_RE_THROW();
}
PLy_curr_procedure = save_curr_proc;
- Py_DECREF(proc->me);
-
return retval;
}
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();
{
PyDict_DelItemString(proc->globals, proc->argnames[i]);
}
+/*
+ * Decide whether a cached PLyProcedure struct is still valid
+ */
+static bool
+PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
+{
+ Assert(proc != NULL);
+
+ /* If the pg_proc tuple has changed, it's not valid */
+ return (proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+ ItemPointerEquals(&proc->fn_tid, &procTup->t_self));
+}
+
/*
* PLyProcedure functions
* function calls.
*/
static PLyProcedure *
-PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid)
+PLy_procedure_get(Oid fn_oid, bool is_trigger)
{
- Oid fn_oid;
HeapTuple procTup;
- char key[128];
- PyObject *plproc;
- PLyProcedure *proc = NULL;
- int rv;
+ PLyProcedureEntry *entry;
+ bool found;
- fn_oid = fcinfo->flinfo->fn_oid;
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
if (!HeapTupleIsValid(procTup))
elog(ERROR, "cache lookup failed for function %u", fn_oid);
- rv = snprintf(key, sizeof(key), "%u_%u", fn_oid, tgreloid);
- if (rv >= sizeof(key) || rv < 0)
- elog(ERROR, "key too long");
-
- plproc = PyDict_GetItemString(PLy_procedure_cache, key);
+ /* 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);
- if (plproc != NULL)
+ PG_TRY();
{
- Py_INCREF(plproc);
- if (!PyCObject_Check(plproc))
- elog(FATAL, "expected a PyCObject, didn't get one");
-
- proc = PyCObject_AsVoidPtr(plproc);
- if (!proc)
- PLy_elog(ERROR, "PyCObject_AsVoidPtr() failed");
- if (proc->me != plproc)
- elog(FATAL, "proc->me != plproc");
- /* did we find an up-to-date cache entry? */
- if (proc->fn_xmin != HeapTupleHeaderGetXmin(procTup->t_data) ||
- !ItemPointerEquals(&proc->fn_tid, &procTup->t_self))
+ 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))
{
- Py_DECREF(plproc);
- proc = NULL;
+ /* 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 */
}
-
- if (proc == NULL)
- proc = PLy_procedure_create(procTup, tgreloid, key);
-
- if (OidIsValid(tgreloid))
+ PG_CATCH();
{
- /*
- * 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.
- */
- TriggerData *tdata = (TriggerData *) fcinfo->context;
-
- Assert(CALLED_AS_TRIGGER(fcinfo));
- PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
- PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
+ /* 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 proc;
+ return entry->proc;
}
+/*
+ * Create a new PLyProcedure structure
+ */
static PLyProcedure *
-PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
+PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
rv;
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-
- if (OidIsValid(tgreloid))
- rv = snprintf(procName, sizeof(procName),
- "__plpython_procedure_%s_%u_trigger_%u",
- NameStr(procStruct->proname),
- HeapTupleGetOid(procTup),
- tgreloid);
- else
- rv = snprintf(procName, sizeof(procName),
- "__plpython_procedure_%s_%u",
- NameStr(procStruct->proname),
- HeapTupleGetOid(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");
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
proc->code = proc->statics = NULL;
- proc->globals = proc->me = NULL;
+ proc->globals = NULL;
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
- if (!OidIsValid(tgreloid))
+ if (!is_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
pfree(procSource);
procSource = NULL;
-
- proc->me = PyCObject_FromVoidPtr(proc, NULL);
- if (!proc->me)
- PLy_elog(ERROR, "PyCObject_FromVoidPtr() failed");
- PyDict_SetItemString(PLy_procedure_cache, key, proc->me);
}
PG_CATCH();
{
return proc;
}
+/*
+ * Insert the procedure into the Python interpreter
+ */
static void
PLy_procedure_compile(PLyProcedure *proc, const char *src)
{
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
free(msrc);
- if (crv != NULL && (!PyErr_Occurred()))
+ if (crv != NULL)
{
int clen;
char call[NAMEDATALEN + 256];
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 && (!PyErr_Occurred()))
+ if (proc->code != NULL)
return;
}
- else
- Py_XDECREF(crv);
PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", proc->proname);
}
Py_XDECREF(proc->code);
Py_XDECREF(proc->statics);
Py_XDECREF(proc->globals);
- Py_XDECREF(proc->me);
if (proc->proname)
PLy_free(proc->proname);
if (proc->pyname)
}
if (proc->argnames)
PLy_free(proc->argnames);
- PLy_free(proc);
}
/*
/* Be sure we do initialization only once (should be redundant now) */
static bool inited = false;
const int **version_ptr;
+ HASHCTL hash_ctl;
if (inited)
return;
PLy_init_plpy();
if (PyErr_Occurred())
PLy_elog(FATAL, "untrapped error in initialization");
- PLy_procedure_cache = PyDict_New();
- if (PLy_procedure_cache == NULL)
- PLy_elog(ERROR, "could not create procedure cache");
+
+ 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);
inited = true;
}