Support polymorphic functions in plpgsql. Along the way, replace
authorTom Lane <[email protected]>
Tue, 1 Jul 2003 21:47:09 +0000 (21:47 +0000)
committerTom Lane <[email protected]>
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);