aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2003-07-01 21:47:09 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2003-07-01 21:47:09 +0000
commitb837c99210697765e9c4589755a514cf5a0a8eec (patch)
treea321c55c58eb6684c8d86452393daf2dfac92ad7
parentcc3002313f9a0b9e1cd562da117c774885a65a0b (diff)
downloadpostgresql-b837c99210697765e9c4589755a514cf5a0a8eec.tar.gz
postgresql-b837c99210697765e9c4589755a514cf5a0a8eec.zip
Support polymorphic functions in plpgsql. Along the way, replace
linked-list search of function cache with hash-table lookup. By Joe Conway.
-rw-r--r--src/pl/plpgsql/src/pl_comp.c340
-rw-r--r--src/pl/plpgsql/src/pl_handler.c87
-rw-r--r--src/pl/plpgsql/src/plpgsql.h19
3 files changed, 314 insertions, 132 deletions
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index d62b237f114..b9867dd6dbf 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.58 2003/05/05 16:46:27 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.59 2003/07/01 21:47:09 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -79,10 +79,38 @@ int plpgsql_DumpExecTree = 0;
PLpgSQL_function *plpgsql_curr_compile;
+/* ----------
+ * Hash table for compiled functions
+ * ----------
+ */
+static HTAB *plpgsql_HashTable = (HTAB *) NULL;
+
+typedef struct plpgsql_hashent
+{
+ PLpgSQL_func_hashkey key;
+ PLpgSQL_function *function;
+} plpgsql_HashEnt;
+
+#define FUNCS_PER_USER 128 /* initial table size */
+
+/* ----------
+ * static prototypes
+ * ----------
+ */
+static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ PLpgSQL_func_hashkey *hashkey);
static void plpgsql_compile_error_callback(void *arg);
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
-
+static void compute_function_hashkey(FmgrInfo *flinfo,
+ Form_pg_proc procStruct,
+ PLpgSQL_func_hashkey *hashkey);
+static void plpgsql_HashTableInit(void);
+static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
+static void plpgsql_HashTableInsert(PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *func_key);
+static void plpgsql_HashTableDelete(PLpgSQL_function *function);
/*
* This routine is a crock, and so is everyplace that calls it. The problem
@@ -103,44 +131,129 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
/* ----------
- * plpgsql_compile Given a pg_proc's oid, make
- * an execution tree for it.
+ * plpgsql_compile Make an execution tree for a PL/pgSQL function.
+ *
+ * Note: it's important for this to fall through quickly if the function
+ * has already been compiled.
* ----------
*/
PLpgSQL_function *
-plpgsql_compile(Oid fn_oid, int functype)
+plpgsql_compile(FunctionCallInfo fcinfo)
{
- int parse_rc;
+ Oid funcOid = fcinfo->flinfo->fn_oid;
HeapTuple procTup;
Form_pg_proc procStruct;
+ PLpgSQL_function *function;
+ PLpgSQL_func_hashkey hashkey;
+ bool hashkey_valid = false;
+
+ /*
+ * Lookup the pg_proc tuple by Oid; we'll need it in any case
+ */
+ procTup = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(funcOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "plpgsql: cache lookup for proc %u failed", funcOid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * See if there's already a cache entry for the current FmgrInfo.
+ * If not, try to find one in the hash table.
+ */
+ function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
+
+ if (!function)
+ {
+ /* First time through in this backend? If so, init hashtable */
+ if (!plpgsql_HashTable)
+ plpgsql_HashTableInit();
+
+ /* Compute hashkey using function signature and actual arg types */
+ compute_function_hashkey(fcinfo->flinfo, procStruct, &hashkey);
+ hashkey_valid = true;
+
+ /* And do the lookup */
+ function = plpgsql_HashTableLookup(&hashkey);
+ }
+
+ if (function)
+ {
+ /* We have a compiled function, but is it still valid? */
+ if (!(function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+ function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)))
+ {
+ /*
+ * Nope, drop the hashtable entry. XXX someday, free all the
+ * subsidiary storage as well.
+ */
+ plpgsql_HashTableDelete(function);
+
+ function = NULL;
+ }
+ }
+
+ /*
+ * If the function wasn't found or was out-of-date, we have to compile it
+ */
+ if (!function)
+ {
+ /*
+ * Calculate hashkey if we didn't already; we'll need it to store
+ * the completed function.
+ */
+ if (!hashkey_valid)
+ compute_function_hashkey(fcinfo->flinfo, procStruct, &hashkey);
+
+ /*
+ * Do the hard part.
+ */
+ function = do_compile(fcinfo, procTup, &hashkey);
+ }
+
+ ReleaseSysCache(procTup);
+
+ /*
+ * Save pointer in FmgrInfo to avoid search on subsequent calls
+ */
+ fcinfo->flinfo->fn_extra = (void *) function;
+
+ /*
+ * Finally return the compiled function
+ */
+ return function;
+}
+
+/*
+ * This is the slow part of plpgsql_compile().
+ */
+static PLpgSQL_function *
+do_compile(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ PLpgSQL_func_hashkey *hashkey)
+{
+ Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ int functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;
+ PLpgSQL_function *function;
+ char *proc_source;
HeapTuple typeTup;
Form_pg_type typeStruct;
- char *proc_source;
- PLpgSQL_function *function;
PLpgSQL_var *var;
PLpgSQL_row *row;
PLpgSQL_rec *rec;
int i;
int arg_varnos[FUNC_MAX_ARGS];
ErrorContextCallback plerrcontext;
+ int parse_rc;
+ Oid rettypeid;
/*
- * Lookup the pg_proc tuple by Oid
- */
- procTup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(fn_oid),
- 0, 0, 0);
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "plpgsql: cache lookup for proc %u failed", fn_oid);
-
- /*
- * Setup the scanner input and error info. We assume that this function
- * cannot be invoked recursively, so there's no need to save and restore
- * the static variables used here.
+ * Setup the scanner input and error info. We assume that this
+ * function cannot be invoked recursively, so there's no need to save
+ * and restore the static variables used here.
*/
- procStruct = (Form_pg_proc) GETSTRUCT(procTup);
proc_source = DatumGetCString(DirectFunctionCall1(textout,
- PointerGetDatum(&procStruct->prosrc)));
+ PointerGetDatum(&procStruct->prosrc)));
plpgsql_scanner_init(proc_source, functype);
pfree(proc_source);
@@ -171,11 +284,11 @@ plpgsql_compile(Oid fn_oid, int functype)
* Create the new function node
*/
function = malloc(sizeof(PLpgSQL_function));
- memset(function, 0, sizeof(PLpgSQL_function));
+ MemSet(function, 0, sizeof(PLpgSQL_function));
plpgsql_curr_compile = function;
function->fn_name = strdup(NameStr(procStruct->proname));
- function->fn_oid = fn_oid;
+ function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
function->fn_functype = functype;
@@ -185,39 +298,55 @@ plpgsql_compile(Oid fn_oid, int functype)
case T_FUNCTION:
/*
+ * Check for a polymorphic returntype. If found, use the actual
+ * returntype type from the caller's FuncExpr node, if we
+ * have one.
+ */
+ rettypeid = procStruct->prorettype;
+ if (rettypeid == ANYARRAYOID || rettypeid == ANYELEMENTOID)
+ {
+ rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
+ if (!OidIsValid(rettypeid))
+ elog(ERROR, "could not determine actual return type "
+ "for polymorphic function %s",
+ plpgsql_error_funcname);
+ }
+
+ /*
* Normal function has a defined returntype
*/
- function->fn_rettype = procStruct->prorettype;
+ function->fn_rettype = rettypeid;
function->fn_retset = procStruct->proretset;
/*
* Lookup the functions return type
*/
typeTup = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(procStruct->prorettype),
+ ObjectIdGetDatum(rettypeid),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup for return type %u failed",
- procStruct->prorettype);
+ rettypeid);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* Disallow pseudotype result, except VOID or RECORD */
+ /* (note we already replaced ANYARRAY/ANYELEMENT) */
if (typeStruct->typtype == 'p')
{
- if (procStruct->prorettype == VOIDOID ||
- procStruct->prorettype == RECORDOID)
- /* okay */ ;
- else if (procStruct->prorettype == TRIGGEROID)
+ if (rettypeid == VOIDOID ||
+ rettypeid == RECORDOID)
+ /* okay */ ;
+ else if (rettypeid == TRIGGEROID)
elog(ERROR, "plpgsql functions cannot return type %s"
"\n\texcept when used as triggers",
- format_type_be(procStruct->prorettype));
+ format_type_be(rettypeid));
else
elog(ERROR, "plpgsql functions cannot return type %s",
- format_type_be(procStruct->prorettype));
+ format_type_be(rettypeid));
}
if (typeStruct->typrelid != InvalidOid ||
- procStruct->prorettype == RECORDOID)
+ rettypeid == RECORDOID)
function->fn_retistuple = true;
else
{
@@ -229,29 +358,39 @@ plpgsql_compile(Oid fn_oid, int functype)
ReleaseSysCache(typeTup);
/*
- * Create the variables for the procedures parameters
+ * Create the variables for the procedure's parameters
*/
for (i = 0; i < procStruct->pronargs; i++)
{
char buf[32];
+ Oid argtypeid;
+
+ /* name for variable */
+ snprintf(buf, sizeof(buf), "$%d", i + 1);
- snprintf(buf, sizeof(buf), "$%d", i + 1); /* name for variable */
+ /*
+ * Since we already did the replacement of polymorphic
+ * argument types by actual argument types while computing
+ * the hashkey, we can just use those results.
+ */
+ argtypeid = hashkey->argtypes[i];
/*
* Get the parameters type
*/
typeTup = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(procStruct->proargtypes[i]),
+ ObjectIdGetDatum(argtypeid),
0, 0, 0);
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup for argument type %u failed",
- procStruct->proargtypes[i]);
+ argtypeid);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
/* Disallow pseudotype argument */
+ /* (note we already replaced ANYARRAY/ANYELEMENT) */
if (typeStruct->typtype == 'p')
elog(ERROR, "plpgsql functions cannot take type %s",
- format_type_be(procStruct->proargtypes[i]));
+ format_type_be(argtypeid));
if (typeStruct->typrelid != InvalidOid)
{
@@ -511,7 +650,14 @@ plpgsql_compile(Oid fn_oid, int functype)
function->datums[i] = plpgsql_Datums[i];
function->action = plpgsql_yylval.program;
- ReleaseSysCache(procTup);
+ /* Debug dump for completed functions */
+ if (plpgsql_DumpExecTree)
+ plpgsql_dumptree(function);
+
+ /*
+ * add it to the hash table
+ */
+ plpgsql_HashTableInsert(function, hashkey);
/*
* Pop the error context stack
@@ -520,11 +666,6 @@ plpgsql_compile(Oid fn_oid, int functype)
plpgsql_error_funcname = NULL;
plpgsql_error_lineno = 0;
- /*
- * Finally return the compiled function
- */
- if (plpgsql_DumpExecTree)
- plpgsql_dumptree(function);
return function;
}
@@ -1500,3 +1641,112 @@ plpgsql_yyerror(const char *s)
plpgsql_error_lineno = plpgsql_scanner_lineno();
elog(ERROR, "%s at or near \"%s\"", s, plpgsql_yytext);
}
+
+
+/*
+ * Compute the hashkey for a given function invocation
+ *
+ * The hashkey is returned into the caller-provided storage at *hashkey.
+ */
+static void
+compute_function_hashkey(FmgrInfo *flinfo,
+ Form_pg_proc procStruct,
+ PLpgSQL_func_hashkey *hashkey)
+{
+ int i;
+
+ /* Make sure any unused bytes of the struct are zero */
+ MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
+
+ hashkey->funcOid = flinfo->fn_oid;
+
+ /* get the argument types */
+ for (i = 0; i < procStruct->pronargs; i++)
+ {
+ Oid argtypeid = procStruct->proargtypes[i];
+
+ /*
+ * Check for polymorphic arguments. If found, use the actual
+ * parameter type from the caller's FuncExpr node, if we
+ * have one.
+ *
+ * We can support arguments of type ANY the same way as normal
+ * polymorphic arguments.
+ */
+ if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
+ argtypeid == ANYOID)
+ {
+ argtypeid = get_fn_expr_argtype(flinfo, i);
+ if (!OidIsValid(argtypeid))
+ elog(ERROR, "could not determine actual argument "
+ "type for polymorphic function %s",
+ NameStr(procStruct->proname));
+ }
+
+ hashkey->argtypes[i] = argtypeid;
+ }
+}
+
+static void
+plpgsql_HashTableInit(void)
+{
+ HASHCTL ctl;
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(PLpgSQL_func_hashkey);
+ ctl.entrysize = sizeof(plpgsql_HashEnt);
+ ctl.hash = tag_hash;
+ plpgsql_HashTable = hash_create("PLpgSQL function cache",
+ FUNCS_PER_USER,
+ &ctl,
+ HASH_ELEM | HASH_FUNCTION);
+}
+
+static PLpgSQL_function *
+plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key)
+{
+ plpgsql_HashEnt *hentry;
+
+ hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable,
+ (void *) func_key,
+ HASH_FIND,
+ NULL);
+ if (hentry)
+ return hentry->function;
+ else
+ return (PLpgSQL_function *) NULL;
+}
+
+static void
+plpgsql_HashTableInsert(PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *func_key)
+{
+ plpgsql_HashEnt *hentry;
+ bool found;
+
+ hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable,
+ (void *) func_key,
+ HASH_ENTER,
+ &found);
+ if (hentry == NULL)
+ elog(ERROR, "out of memory in plpgsql_HashTable");
+ if (found)
+ elog(WARNING, "trying to insert a function that exists");
+
+ hentry->function = function;
+ /* prepare back link from function to hashtable key */
+ function->fn_hashkey = &hentry->key;
+}
+
+static void
+plpgsql_HashTableDelete(PLpgSQL_function *function)
+{
+ plpgsql_HashEnt *hentry;
+
+ hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable,
+ (void *) function->fn_hashkey,
+ HASH_REMOVE,
+ NULL);
+ if (hentry == NULL)
+ elog(WARNING, "trying to delete function that does not exist");
+}
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 17b9cf2e42b..592877fe52b 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.12 2002/08/30 00:28:41 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.13 2003/07/01 21:47:09 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -45,15 +45,6 @@
#include "utils/syscache.h"
-/*
- * Head of list of already-compiled functions
- */
-static PLpgSQL_function *compiled_functions = NULL;
-
-
-static bool func_up_to_date(PLpgSQL_function * func);
-
-
/* ----------
* plpgsql_call_handler
*
@@ -67,8 +58,6 @@ PG_FUNCTION_INFO_V1(plpgsql_call_handler);
Datum
plpgsql_call_handler(PG_FUNCTION_ARGS)
{
- bool isTrigger = CALLED_AS_TRIGGER(fcinfo);
- Oid funcOid = fcinfo->flinfo->fn_oid;
PLpgSQL_function *func;
Datum retval;
@@ -78,55 +67,14 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "plpgsql: cannot connect to SPI manager");
- /*
- * Check if we already compiled this function and saved the pointer
- * (ie, current FmgrInfo has been used before)
- */
- func = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
- if (func != NULL)
- {
- Assert(func->fn_oid == funcOid);
-
- /*
- * But is the function still up to date?
- */
- if (!func_up_to_date(func))
- func = NULL;
- }
-
- if (func == NULL)
- {
- /*
- * Check if we already compiled this function for another caller
- */
- for (func = compiled_functions; func != NULL; func = func->next)
- {
- if (funcOid == func->fn_oid && func_up_to_date(func))
- break;
- }
-
- /*
- * If not, do so and add it to the compiled ones
- */
- if (func == NULL)
- {
- func = plpgsql_compile(funcOid,
- isTrigger ? T_TRIGGER : T_FUNCTION);
- func->next = compiled_functions;
- compiled_functions = func;
- }
-
- /*
- * Save pointer in FmgrInfo to avoid search on subsequent calls
- */
- fcinfo->flinfo->fn_extra = (void *) func;
- }
+ /* Find or compile the function */
+ func = plpgsql_compile(fcinfo);
/*
* Determine if called as function or trigger and call appropriate
* subhandler
*/
- if (isTrigger)
+ if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plpgsql_exec_trigger(func,
(TriggerData *) fcinfo->context));
else
@@ -140,30 +88,3 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
return retval;
}
-
-
-/*
- * Check to see if a compiled function is still up-to-date. This
- * is needed because CREATE OR REPLACE FUNCTION can modify the
- * function's pg_proc entry without changing its OID.
- */
-static bool
-func_up_to_date(PLpgSQL_function * func)
-{
- HeapTuple procTup;
- bool result;
-
- procTup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(func->fn_oid),
- 0, 0, 0);
- if (!HeapTupleIsValid(procTup))
- elog(ERROR, "plpgsql: cache lookup for proc %u failed",
- func->fn_oid);
-
- result = (func->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- func->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
-
- ReleaseSysCache(procTup);
-
- return result;
-}
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index e10dd18ac8f..ae4d8909167 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.36 2003/05/05 16:46:28 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.37 2003/07/01 21:47:09 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -487,6 +487,18 @@ typedef struct
} PLpgSQL_stmt_dynexecute;
+typedef struct PLpgSQL_func_hashkey
+{ /* Hash lookup key for functions */
+ Oid funcOid;
+ /*
+ * We include actual argument types in the hash key to support
+ * polymorphic PLpgSQL functions. Be careful that extra positions
+ * are zeroed!
+ */
+ Oid argtypes[FUNC_MAX_ARGS];
+} PLpgSQL_func_hashkey;
+
+
typedef struct PLpgSQL_function
{ /* Complete compiled function */
char *fn_name;
@@ -494,6 +506,7 @@ typedef struct PLpgSQL_function
TransactionId fn_xmin;
CommandId fn_cmin;
int fn_functype;
+ PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
Oid fn_rettype;
int fn_rettyplen;
@@ -519,8 +532,6 @@ typedef struct PLpgSQL_function
int ndatums;
PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action;
-
- struct PLpgSQL_function *next; /* for chaining list of functions */
} PLpgSQL_function;
@@ -588,7 +599,7 @@ extern PLpgSQL_function *plpgsql_curr_compile;
* Functions in pl_comp.c
* ----------
*/
-extern PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype);
+extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo);
extern int plpgsql_parse_word(char *word);
extern int plpgsql_parse_dblword(char *word);
extern int plpgsql_parse_tripword(char *word);