postgresql/src/pl/plpython/plpy_procedure.c
Tom Lane e56bce5d43 Reconsider the handling of procedure OUT parameters.
Commit 2453ea142 redefined pg_proc.proargtypes to include the types of
OUT parameters, for procedures only.  While that had some advantages
for implementing the SQL-spec behavior of DROP PROCEDURE, it was pretty
disastrous from a number of other perspectives.  Notably, since the
primary key of pg_proc is name + proargtypes, this made it possible to
have multiple procedures with identical names + input arguments and
differing output argument types.  That would make it impossible to call
any one of the procedures by writing just NULL (or "?", or any other
data-type-free notation) for the output argument(s).  The change also
seems likely to cause grave confusion for client applications that
examine pg_proc and expect the traditional definition of proargtypes.

Hence, revert the definition of proargtypes to what it was, and
undo a number of complications that had been added to support that.

To support the SQL-spec behavior of DROP PROCEDURE, when there are
no argmode markers in the command's parameter list, we perform the
lookup both ways (that is, matching against both proargtypes and
proallargtypes), succeeding if we get just one unique match.
In principle this could result in ambiguous-function failures
that would not happen when using only one of the two rules.
However, overloading of procedure names is thought to be a pretty
rare usage, so this shouldn't cause many problems in practice.
Postgres-specific code such as pg_dump can defend against any
possibility of such failures by being careful to specify argmodes
for all procedure arguments.

This also fixes a few other bugs in the area of CALL statements
with named parameters, and improves the documentation a little.

catversion bump forced because the representation of procedures
with OUT arguments changes.

Discussion: https://postgr.es/m/3742981.1621533210@sss.pgh.pa.us
2021-06-10 17:11:36 -04:00

472 lines
12 KiB
C

/*
* 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 = SysCacheGetAttr(PROCOID, procTup,
Anum_pg_proc_prosrc, &isnull);
if (isnull)
elog(ERROR, "null 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;
}