diff options
Diffstat (limited to 'src/pl/plpython/plpy_procedure.c')
-rw-r--r-- | src/pl/plpython/plpy_procedure.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c new file mode 100644 index 00000000000..b4f2abe262d --- /dev/null +++ b/src/pl/plpython/plpy_procedure.c @@ -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; +} |