]> granicus.if.org Git - postgresql/commitdiff
Support polymorphic functions in plpgsql. Along the way, replace
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 1 Jul 2003 21:47:09 +0000 (21:47 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 1 Jul 2003 21:47:09 +0000 (21:47 +0000)
linked-list search of function cache with hash-table lookup.
By Joe Conway.

src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/plpgsql.h

index d62b237f1145975e0e381a7dfb5edb496e2e6a6e..b9867dd6dbf3a4fe00b7f744ffa48023285aad63 100644 (file)
@@ -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;
@@ -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;
+
+                               /* 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");
+}
index 17b9cf2e42b4c77e1f5770b400670fccc3f6dab3..592877fe52b65c92fc8c22b660b9fa8157884ee4 100644 (file)
@@ -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.
  *
 #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;
-}
index e10dd18ac8f64238bfe5952e43d074a9a059a72d..ae4d890916756a8232c030e578fb88cd27e94a10 100644 (file)
@@ -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);