From b837c99210697765e9c4589755a514cf5a0a8eec Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Jul 2003 21:47:09 +0000 Subject: [PATCH] Support polymorphic functions in plpgsql. Along the way, replace linked-list search of function cache with hash-table lookup. By Joe Conway. --- src/pl/plpgsql/src/pl_comp.c | 340 +++++++++++++++++++++++++++----- src/pl/plpgsql/src/pl_handler.c | 87 +------- src/pl/plpgsql/src/plpgsql.h | 19 +- 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 d62b237f11..b9867dd6db 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 + * 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. */ - 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. - */ - 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; @@ -184,40 +297,56 @@ 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; - snprintf(buf, sizeof(buf), "$%d", i + 1); /* name for variable */ + /* name for variable */ + snprintf(buf, sizeof(buf), "$%d", i + 1); + + /* + * 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 17b9cf2e42..592877fe52 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 e10dd18ac8..ae4d890916 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);