summaryrefslogtreecommitdiffstats
path: root/src/pl/plpython/plpy_procedure.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl/plpython/plpy_procedure.c')
-rw-r--r--src/pl/plpython/plpy_procedure.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
new file mode 100644
index 0000000..79b6ef6
--- /dev/null
+++ b/src/pl/plpython/plpy_procedure.c
@@ -0,0 +1,470 @@
+/*
+ * Python procedure manipulation for plpython
+ *
+ * src/pl/plpython/plpy_procedure.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "funcapi.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_procedure.h"
+#include "plpython.h"
+#include "utils/builtins.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+static HTAB *PLy_procedure_cache = NULL;
+
+static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
+static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
+static char *PLy_procedure_munge_source(const char *name, const char *src);
+
+
+void
+init_procedure_caches(void)
+{
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = sizeof(PLyProcedureKey);
+ hash_ctl.entrysize = sizeof(PLyProcedureEntry);
+ PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * PLy_procedure_name: get the name of the specified 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.
+ *
+ * fn_oid is the OID of the function requested
+ * fn_rel is InvalidOid or the relation this function triggers on
+ * is_trigger denotes whether the function is a trigger function
+ *
+ * The reason that both fn_rel and is_trigger need to be passed is that when
+ * trigger functions get validated we don't know which relation(s) they'll
+ * be used with, so no sensible fn_rel can be passed.
+ */
+PLyProcedure *
+PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
+{
+ bool use_cache = !(is_trigger && fn_rel == InvalidOid);
+ HeapTuple procTup;
+ PLyProcedureKey key;
+ PLyProcedureEntry *volatile entry = NULL;
+ PLyProcedure *volatile proc = NULL;
+ bool found = false;
+
+ 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 cache, unless we don't have the necessary
+ * information (e.g. during validation). In that case we just don't cache
+ * anything.
+ */
+ if (use_cache)
+ {
+ key.fn_oid = fn_oid;
+ key.fn_rel = fn_rel;
+ entry = hash_search(PLy_procedure_cache, &key, HASH_ENTER, &found);
+ proc = entry->proc;
+ }
+
+ PG_TRY();
+ {
+ if (!found)
+ {
+ /* Haven't found it, create a new procedure */
+ proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+ if (use_cache)
+ entry->proc = proc;
+ }
+ else if (!PLy_procedure_valid(proc, procTup))
+ {
+ /* Found it, but it's invalid, free and reuse the cache entry */
+ entry->proc = NULL;
+ if (proc)
+ PLy_procedure_delete(proc);
+ proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+ entry->proc = proc;
+ }
+ /* Found it and it's valid, it's fine to use it */
+ }
+ PG_CATCH();
+ {
+ /* Do not leave an uninitialized entry in the cache */
+ if (use_cache)
+ hash_search(PLy_procedure_cache, &key, HASH_REMOVE, NULL);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ ReleaseSysCache(procTup);
+
+ return 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;
+ MemoryContext cxt;
+ MemoryContext oldcxt;
+ int rv;
+ char *ptr;
+
+ 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");
+
+ /* Replace any not-legal-in-Python-names characters with '_' */
+ for (ptr = procName; *ptr; ptr++)
+ {
+ if (!((*ptr >= 'A' && *ptr <= 'Z') ||
+ (*ptr >= 'a' && *ptr <= 'z') ||
+ (*ptr >= '0' && *ptr <= '9')))
+ *ptr = '_';
+ }
+
+ /* Create long-lived context that all procedure info will live in */
+ cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Python function",
+ ALLOCSET_DEFAULT_SIZES);
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure));
+ proc->mcxt = cxt;
+
+ PG_TRY();
+ {
+ Datum protrftypes_datum;
+ Datum prosrcdatum;
+ bool isnull;
+ char *procSource;
+ int i;
+
+ proc->proname = pstrdup(NameStr(procStruct->proname));
+ MemoryContextSetIdentifier(cxt, proc->proname);
+ proc->pyname = pstrdup(procName);
+ proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ proc->fn_tid = procTup->t_self;
+ proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+ proc->is_setof = procStruct->proretset;
+ proc->is_procedure = (procStruct->prokind == PROKIND_PROCEDURE);
+ proc->src = NULL;
+ proc->argnames = NULL;
+ proc->args = NULL;
+ proc->nargs = 0;
+ proc->langid = procStruct->prolang;
+ protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes,
+ &isnull);
+ proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+ proc->code = NULL;
+ proc->statics = NULL;
+ proc->globals = NULL;
+ proc->calldepth = 0;
+ proc->argstack = NULL;
+
+ /*
+ * get information required for output conversion of the return value,
+ * but only if this isn't a trigger.
+ */
+ if (!is_trigger)
+ {
+ Oid rettype = procStruct->prorettype;
+ HeapTuple rvTypeTup;
+ Form_pg_type rvTypeStruct;
+
+ rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
+ if (!HeapTupleIsValid(rvTypeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettype);
+ rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
+
+ /* Disallow pseudotype result, except for void or record */
+ if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
+ {
+ if (rettype == VOIDOID ||
+ rettype == RECORDOID)
+ /* okay */ ;
+ else if (rettype == TRIGGEROID || rettype == EVENT_TRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Python functions cannot return type %s",
+ format_type_be(rettype))));
+ }
+
+ /* set up output function for procedure result */
+ PLy_output_setup_func(&proc->result, proc->mcxt,
+ rettype, -1, proc);
+
+ ReleaseSysCache(rvTypeTup);
+ }
+ else
+ {
+ /*
+ * In a trigger function, we use proc->result and proc->result_in
+ * for converting tuples, but we don't yet have enough info to set
+ * them up. PLy_exec_trigger will deal with it.
+ */
+ proc->result.typoid = InvalidOid;
+ proc->result_in.typoid = InvalidOid;
+ }
+
+ /*
+ * 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 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)++;
+ }
+ }
+
+ /* Allocate arrays for per-input-argument data */
+ proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
+ proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * 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);
+
+ /* disallow pseudotype arguments */
+ if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Python functions cannot accept type %s",
+ format_type_be(types[i]))));
+
+ /* set up I/O function info */
+ PLy_input_setup_func(&proc->args[pos], proc->mcxt,
+ types[i], -1, /* typmod not known */
+ proc);
+
+ /* get argument name */
+ proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
+
+ ReleaseSysCache(argTypeTup);
+
+ pos++;
+ }
+ }
+
+ /*
+ * get the text of the function.
+ */
+ prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procTup,
+ Anum_pg_proc_prosrc);
+ procSource = TextDatumGetCString(prosrcdatum);
+
+ PLy_procedure_compile(proc, procSource);
+
+ pfree(procSource);
+ }
+ PG_CATCH();
+ {
+ MemoryContextSwitchTo(oldcxt);
+ PLy_procedure_delete(proc);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ MemoryContextSwitchTo(oldcxt);
+ 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();
+ if (!proc->statics)
+ PLy_elog(ERROR, NULL);
+ 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 = MemoryContextStrdup(proc->mcxt, 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)
+{
+ Py_XDECREF(proc->code);
+ Py_XDECREF(proc->statics);
+ Py_XDECREF(proc->globals);
+ MemoryContextDelete(proc->mcxt);
+}
+
+/*
+ * Decide whether a cached PLyProcedure struct is still valid
+ */
+static bool
+PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
+{
+ if (proc == NULL)
+ return false;
+
+ /* If the pg_proc tuple has changed, it's not valid */
+ if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
+ return false;
+
+ return true;
+}
+
+static char *
+PLy_procedure_munge_source(const char *name, const char *src)
+{
+ char *mrc,
+ *mp;
+ const char *sp;
+ size_t mlen;
+ int 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_procedure_munge_source");
+
+ return mrc;
+}