diff options
Diffstat (limited to 'src/pl/plpython/plpy_procedure.c')
-rw-r--r-- | src/pl/plpython/plpy_procedure.c | 470 |
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; +} |