Re: polymorphic arguments and return type for PL/pgSQL

Поиск
Список
Период
Сортировка
От Joe Conway
Тема Re: polymorphic arguments and return type for PL/pgSQL
Дата
Msg-id 3F00C462.6090900@joeconway.com
обсуждение исходный текст
Ответ на Re: polymorphic arguments and return type for PL/pgSQL  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: polymorphic arguments and return type for PL/pgSQL  (Joe Conway <mail@joeconway.com>)
Список pgsql-patches
Tom Lane wrote:
 > Joe Conway wrote:
>>2) create hash key using a new structure that includes function oid,
>>return type, and argument types, and use that for direct lookup.
>
> The latter.  By the time you pay the price of a hash lookup, a slightly
> longer key is nearly free.  (Maybe entirely free, since it might produce
> better-distributed hash values.)
>
> dynahash only supports fixed-length keys, so don't forget to zero out
> unused positions in the argument type vector.
>
> BTW, I can't see any need to include the return type in the hash key ---
> wouldn't it be predetermined given the argument types?
>

The attached implements a compiled function hash in addition to the
earlier changes to support PL/pgSQL polymorphism. It also includes the
ealier requested change wrt generating an ERROR when faced with
polymorphic arguments or return type and no FuncExpr node available.

The compiled function hash uses the following key:
typedef struct PLpgSQL_func_key
{
   Oid  funcOid;
   Oid  argtypes[FUNC_MAX_ARGS];
} PLpgSQL_func_key;

I did a simple test to check performance impact using the ealier sample
function and table:

CREATE OR REPLACE FUNCTION tst(anyelement) returns anyarray as '
begin
  if $1 is of (int2, int4, int8, float4, float8, numeric) then
    return array[$1 * 2];
  elsif $1 is of (text) then
    return array[$1 || $1];
  else
    return array[$1];
  end if;
end;
' language 'plpgsql';

create table plpgsql(f1 int, f2 float8, f3 text, f4 oid);
insert into plpgsql values(1, 1.1, 'a', 1);
insert into plpgsql values(2, 2.2, 'b', 2);

---------------------------------------------------------
with original patch (linked list compiled function cache)
---------------------------------------------------------
psql regression
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 3.73 msec
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 1.89 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 1.36 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.70 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.21 msec
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.18 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.18 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.18 msec
\q

----------------------------------------------------
with this patch (hash table compiled function cache)
----------------------------------------------------
psql regression
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 2.93 msec
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 1.64 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.18 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 1.05 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.69 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f1) from plpgsql; Total runtime: 0.19 msec
explain analyze SELECT tst(f2) from plpgsql; Total runtime: 0.18 msec
explain analyze SELECT tst(f3) from plpgsql; Total runtime: 0.21 msec
explain analyze SELECT tst(f4) from plpgsql; Total runtime: 0.22 msec
\q

No difference worth caring about. In more complex scenarios, the hash
table cache should win hands down, I'd think.

Compiles clean, and passes all regression tests. I'll look to update the
docs and regression tests as part of my post freeze array/polymorphic
function cleanup.

If there are no objections, please apply.

Thanks,

Joe
Index: src/pl/plpgsql/src/pl_comp.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_comp.c,v
retrieving revision 1.58
diff -c -r1.58 pl_comp.c
*** src/pl/plpgsql/src/pl_comp.c    5 May 2003 16:46:27 -0000    1.58
--- src/pl/plpgsql/src/pl_comp.c    30 Jun 2003 22:20:15 -0000
***************
*** 79,88 ****

  PLpgSQL_function *plpgsql_curr_compile;


  static void plpgsql_compile_error_callback(void *arg);
  static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
!

  /*
   * This routine is a crock, and so is everyplace that calls it.  The problem
--- 79,118 ----

  PLpgSQL_function *plpgsql_curr_compile;

+ /* ----------
+  * compiled function hash table related
+  * ----------
+  */
+ static HTAB *plpgsql_HashTable = (HTAB *) NULL;
+
+ typedef struct PLpgSQL_func_key
+ {
+     Oid        funcOid;
+     Oid        argtypes[FUNC_MAX_ARGS];
+ } PLpgSQL_func_key;
+
+ typedef struct plpgsql_hashent
+ {
+     PLpgSQL_func_key    func_key;
+     PLpgSQL_function   *function;
+ } plpgsql_HashEnt;

+ #define FUNCS_PER_USER        128
+
+
+ /* ----------
+  * static prototypes
+  * ----------
+  */
  static void plpgsql_compile_error_callback(void *arg);
  static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
! static PLpgSQL_function *get_function_by_signature(FunctionCallInfo fcinfo,
!                                                    HeapTuple procTup);
! static void plpgsql_HashTableInit(void);
! static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_key *func_key);
! static void plpgsql_HashTableInsert(PLpgSQL_function *function);
! static void plpgsql_HashTableDelete(PLpgSQL_function *function);
! static PLpgSQL_func_key *function_get_func_key(PLpgSQL_function *function);

  /*
   * This routine is a crock, and so is everyplace that calls it.  The problem
***************
*** 108,114 ****
   * ----------
   */
  PLpgSQL_function *
! plpgsql_compile(Oid fn_oid, int functype)
  {
      int            parse_rc;
      HeapTuple    procTup;
--- 138,144 ----
   * ----------
   */
  PLpgSQL_function *
! plpgsql_compile(FunctionCallInfo fcinfo)
  {
      int            parse_rc;
      HeapTuple    procTup;
***************
*** 123,530 ****
      int            i;
      int            arg_varnos[FUNC_MAX_ARGS];
      ErrorContextCallback plerrcontext;

      /*
!      * 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.
!      */
!     procStruct = (Form_pg_proc) GETSTRUCT(procTup);
!     proc_source = DatumGetCString(DirectFunctionCall1(textout,
!                                   PointerGetDatum(&procStruct->prosrc)));
!     plpgsql_scanner_init(proc_source, functype);
!     pfree(proc_source);
!
!     plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
!     plpgsql_error_lineno = 0;

      /*
!      * Setup error traceback support for ereport()
       */
!     plerrcontext.callback = plpgsql_compile_error_callback;
!     plerrcontext.arg = NULL;
!     plerrcontext.previous = error_context_stack;
!     error_context_stack = &plerrcontext;

      /*
!      * Initialize the compiler
       */
!     plpgsql_ns_init();
!     plpgsql_ns_push(NULL);
!     plpgsql_DumpExecTree = 0;
!
!     datums_alloc = 128;
!     plpgsql_nDatums = 0;
!     plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
!     datums_last = 0;

!     /*
!      * Create the new function node
!      */
!     function = malloc(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_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
!     function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
!     function->fn_functype = functype;

!     switch (functype)
!     {
!         case T_FUNCTION:

!             /*
!              * Normal function has a defined returntype
!              */
!             function->fn_rettype = procStruct->prorettype;
!             function->fn_retset = procStruct->proretset;

!             /*
!              * Lookup the functions return type
!              */
!             typeTup = SearchSysCache(TYPEOID,
!                                 ObjectIdGetDatum(procStruct->prorettype),
!                                      0, 0, 0);
!             if (!HeapTupleIsValid(typeTup))
!                 elog(ERROR, "cache lookup for return type %u failed",
!                      procStruct->prorettype);
!             typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

!             /* Disallow pseudotype result, except VOID or RECORD */
!             if (typeStruct->typtype == 'p')
!             {
!                 if (procStruct->prorettype == VOIDOID ||
!                     procStruct->prorettype == RECORDOID)
!                      /* okay */ ;
!                 else if (procStruct->prorettype == TRIGGEROID)
!                     elog(ERROR, "plpgsql functions cannot return type %s"
!                          "\n\texcept when used as triggers",
!                          format_type_be(procStruct->prorettype));
                  else
!                     elog(ERROR, "plpgsql functions cannot return type %s",
!                          format_type_be(procStruct->prorettype));
!             }
!
!             if (typeStruct->typrelid != InvalidOid ||
!                 procStruct->prorettype == RECORDOID)
!                 function->fn_retistuple = true;
!             else
!             {
!                 function->fn_retbyval = typeStruct->typbyval;
!                 function->fn_rettyplen = typeStruct->typlen;
!                 function->fn_rettypelem = typeStruct->typelem;
!                 perm_fmgr_info(typeStruct->typinput, &(function->fn_retinput));
!             }
!             ReleaseSysCache(typeTup);
!
!             /*
!              * Create the variables for the procedures parameters
!              */
!             for (i = 0; i < procStruct->pronargs; i++)
!             {
!                 char        buf[32];

!                 snprintf(buf, sizeof(buf), "$%d", i + 1);        /* name for variable */

                  /*
!                  * Get the parameters type
                   */
                  typeTup = SearchSysCache(TYPEOID,
!                             ObjectIdGetDatum(procStruct->proargtypes[i]),
                                           0, 0, 0);
                  if (!HeapTupleIsValid(typeTup))
!                     elog(ERROR, "cache lookup for argument type %u failed",
!                          procStruct->proargtypes[i]);
                  typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

!                 /* Disallow pseudotype argument */
                  if (typeStruct->typtype == 'p')
!                     elog(ERROR, "plpgsql functions cannot take type %s",
!                          format_type_be(procStruct->proargtypes[i]));

!                 if (typeStruct->typrelid != InvalidOid)
                  {
                      /*
!                      * For tuple type parameters, we set up a record of
!                      * that type
                       */
!                     row = plpgsql_build_rowtype(typeStruct->typrelid);
!
!                     row->refname = strdup(buf);

!                     plpgsql_adddatum((PLpgSQL_datum *) row);
!                     plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, row->rowno,
!                                        row->refname);

-                     arg_varnos[i] = row->rowno;
-                 }
-                 else
-                 {
                      /*
!                      * Normal parameters get a var node
                       */
!                     var = malloc(sizeof(PLpgSQL_var));
!                     memset(var, 0, sizeof(PLpgSQL_var));

!                     var->dtype = PLPGSQL_DTYPE_VAR;
!                     var->refname = strdup(buf);
!                     var->lineno = 0;
!                     var->datatype = build_datatype(typeTup, -1);
!                     var->isconst = true;
!                     var->notnull = false;
!                     var->default_val = NULL;
!
!                     plpgsql_adddatum((PLpgSQL_datum *) var);
!                     plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno,
!                                        var->refname);

!                     arg_varnos[i] = var->varno;
                  }
!                 ReleaseSysCache(typeTup);
!             }
!             break;

!         case T_TRIGGER:

!             /*
!              * Trigger procedures return type is unknown yet
!              */
!             function->fn_rettype = InvalidOid;
!             function->fn_retbyval = false;
!             function->fn_retistuple = true;
!             function->fn_retset = false;

!             /*
!              * Add the record for referencing NEW
!              */
!             rec = malloc(sizeof(PLpgSQL_rec));
!             memset(rec, 0, sizeof(PLpgSQL_rec));
!             rec->dtype = PLPGSQL_DTYPE_REC;
!             rec->refname = strdup("new");
!             rec->tup = NULL;
!             rec->tupdesc = NULL;
!             rec->freetup = false;
!             plpgsql_adddatum((PLpgSQL_datum *) rec);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
!             function->new_varno = rec->recno;

!             /*
!              * Add the record for referencing OLD
!              */
!             rec = malloc(sizeof(PLpgSQL_rec));
!             memset(rec, 0, sizeof(PLpgSQL_rec));
!             rec->dtype = PLPGSQL_DTYPE_REC;
!             rec->refname = strdup("old");
!             rec->tup = NULL;
!             rec->tupdesc = NULL;
!             rec->freetup = false;
!             plpgsql_adddatum((PLpgSQL_datum *) rec);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
!             function->old_varno = rec->recno;

!             /*
!              * Add the variable tg_name
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_name");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("name");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_name_varno = var->varno;

!             /*
!              * Add the variable tg_when
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_when");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("text");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_when_varno = var->varno;

!             /*
!              * Add the variable tg_level
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_level");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("text");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_level_varno = var->varno;

!             /*
!              * Add the variable tg_op
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_op");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("text");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_op_varno = var->varno;

!             /*
!              * Add the variable tg_relid
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_relid");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("oid");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_relid_varno = var->varno;

!             /*
!              * Add the variable tg_relname
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_relname");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("name");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_relname_varno = var->varno;

!             /*
!              * Add the variable tg_nargs
!              */
!             var = malloc(sizeof(PLpgSQL_var));
!             memset(var, 0, sizeof(PLpgSQL_var));

!             var->dtype = PLPGSQL_DTYPE_VAR;
!             var->refname = strdup("tg_nargs");
!             var->lineno = 0;
!             var->datatype = plpgsql_parse_datatype("int4");
!             var->isconst = false;
!             var->notnull = false;
!             var->default_val = NULL;
!
!             plpgsql_adddatum((PLpgSQL_datum *) var);
!             plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!             function->tg_nargs_varno = var->varno;

!             break;

!         default:
!             elog(ERROR, "unknown function type %u in plpgsql_compile()",
!                  functype);
!             break;
!     }

!     /*
!      * Create the magic FOUND variable.
!      */
!     var = malloc(sizeof(PLpgSQL_var));
!     memset(var, 0, sizeof(PLpgSQL_var));

!     var->dtype = PLPGSQL_DTYPE_VAR;
!     var->refname = strdup("found");
!     var->lineno = 0;
!     var->datatype = plpgsql_parse_datatype("bool");
!     var->isconst = false;
!     var->notnull = false;
!     var->default_val = NULL;

!     plpgsql_adddatum((PLpgSQL_datum *) var);
!     plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!     function->found_varno = var->varno;

!     /*
!      * Forget about the above created variables
!      */
!     plpgsql_add_initdatums(NULL);

!     /*
!      * Now parse the functions text
!      */
!     parse_rc = plpgsql_yyparse();
!     if (parse_rc != 0)
!         elog(ERROR, "plpgsql: parser returned %d ???", parse_rc);

!     plpgsql_scanner_finish();

!     /*
!      * If that was successful, complete the functions info.
!      */
!     function->fn_nargs = procStruct->pronargs;
!     for (i = 0; i < function->fn_nargs; i++)
!         function->fn_argvarnos[i] = arg_varnos[i];
!     function->ndatums = plpgsql_nDatums;
!     function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
!     for (i = 0; i < plpgsql_nDatums; i++)
!         function->datums[i] = plpgsql_Datums[i];
!     function->action = plpgsql_yylval.program;

      ReleaseSysCache(procTup);

      /*
!      * Pop the error context stack
       */
!     error_context_stack = plerrcontext.previous;
!     plpgsql_error_funcname = NULL;
!     plpgsql_error_lineno = 0;

      /*
       * Finally return the compiled function
       */
      if (plpgsql_DumpExecTree)
          plpgsql_dumptree(function);
      return function;
  }

--- 153,625 ----
      int            i;
      int            arg_varnos[FUNC_MAX_ARGS];
      ErrorContextCallback plerrcontext;
+     Oid            rettypeid;
+     Oid            funcOid = fcinfo->flinfo->fn_oid;
+     int            functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;

      /*
!      * 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);

      /*
!      * Lookup the function in the hashtable based on funcOid and
!      * argument type signature. Create the hash table if this
!      * is the first time through.
       */
!     function = get_function_by_signature(fcinfo, procTup);

      /*
!      * if the function wasn't found or was out-of-date,
!      * we have to compile it
       */
!     if (!function)
!     {
!         /*
!          * 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)));
!         plpgsql_scanner_init(proc_source, functype);
!         pfree(proc_source);

!         plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
!         plpgsql_error_lineno = 0;

!         /*
!          * Setup error traceback support for ereport()
!          */
!         plerrcontext.callback = plpgsql_compile_error_callback;
!         plerrcontext.arg = NULL;
!         plerrcontext.previous = error_context_stack;
!         error_context_stack = &plerrcontext;

!         /*
!          * Initialize the compiler
!          */
!         plpgsql_ns_init();
!         plpgsql_ns_push(NULL);
!         plpgsql_DumpExecTree = 0;
!
!         datums_alloc = 128;
!         plpgsql_nDatums = 0;
!         plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
!         datums_last = 0;

!         /*
!          * Create the new function node
!          */
!         function = malloc(sizeof(PLpgSQL_function));
!         memset(function, 0, sizeof(PLpgSQL_function));
!         plpgsql_curr_compile = function;
!
!         function->fn_name = strdup(NameStr(procStruct->proname));
!         function->fn_oid = funcOid;
!         function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
!         function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
!         function->fn_functype = functype;

!         switch (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.
!                  */
!                 if (procStruct->prorettype == ANYARRAYOID ||
!                     procStruct->prorettype == ANYELEMENTOID)
!                 {
!                     rettypeid = get_fn_expr_rettype(fcinfo);
!                     if (!OidIsValid(rettypeid))
!                         elog(ERROR, "could not determine actual return type " \
!                                     "for polymorphic function %s",
!                                      plpgsql_error_funcname);
!                 }
                  else
!                     rettypeid = procStruct->prorettype;

!                 /*
!                  * Normal function has a defined returntype
!                  */
!                 function->fn_rettype = rettypeid;
!                 function->fn_retset = procStruct->proretset;

                  /*
!                  * Lookup the functions return type
                   */
                  typeTup = SearchSysCache(TYPEOID,
!                                     ObjectIdGetDatum(rettypeid),
                                           0, 0, 0);
                  if (!HeapTupleIsValid(typeTup))
!                     elog(ERROR, "cache lookup for return type %u failed",
!                          rettypeid);
                  typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

!                 /* Disallow pseudotype result, except VOID or RECORD */
                  if (typeStruct->typtype == 'p')
!                 {
!                     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(rettypeid));
!                     else
!                         elog(ERROR, "plpgsql functions cannot return type %s",
!                              format_type_be(rettypeid));
!                 }
!
!                 if (typeStruct->typrelid != InvalidOid ||
!                     rettypeid == RECORDOID)
!                     function->fn_retistuple = true;
!                 else
!                 {
!                     function->fn_retbyval = typeStruct->typbyval;
!                     function->fn_rettyplen = typeStruct->typlen;
!                     function->fn_rettypelem = typeStruct->typelem;
!                     perm_fmgr_info(typeStruct->typinput, &(function->fn_retinput));
!                 }
!                 ReleaseSysCache(typeTup);

!                 /*
!                  * Create the variables for the procedures parameters
!                  */
!                 for (i = 0; i < procStruct->pronargs; i++)
                  {
+                     char        buf[32];
+                     Oid            argtypeid = InvalidOid;
+
+                     snprintf(buf, sizeof(buf), "$%d", i + 1);        /* name for variable */
+
                      /*
!                      * Check for polymorphic arguments. If found, use the actual
!                      * parameter type from the caller's FuncExpr node, if we
!                      * have one.
                       */
!                     if (procStruct->proargtypes[i] == ANYARRAYOID ||
!                         procStruct->proargtypes[i] == ANYELEMENTOID)
!                     {
!                         argtypeid = get_fn_expr_argtype(fcinfo, i);
!                         if (!OidIsValid(argtypeid))
!                             elog(ERROR, "could not determine actual argument " \
!                                         "type for polymorphic function %s",
!                                          plpgsql_error_funcname);
!                     }
!                     else
!                         argtypeid = procStruct->proargtypes[i];

!                     function->fn_argtypes[i] = argtypeid;

                      /*
!                      * Get the parameters type
                       */
!                     typeTup = SearchSysCache(TYPEOID,
!                                 ObjectIdGetDatum(argtypeid),
!                                              0, 0, 0);
!                     if (!HeapTupleIsValid(typeTup))
!                         elog(ERROR, "cache lookup for argument type %u failed",
!                              argtypeid);
!                     typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
!
!                     /* Disallow pseudotype argument */
!                     if (typeStruct->typtype == 'p')
!                         elog(ERROR, "plpgsql functions cannot take type %s",
!                              format_type_be(argtypeid));

!                     if (typeStruct->typrelid != InvalidOid)
!                     {
!                         /*
!                          * For tuple type parameters, we set up a record of
!                          * that type
!                          */
!                         row = plpgsql_build_rowtype(typeStruct->typrelid);
!
!                         row->refname = strdup(buf);
!
!                         plpgsql_adddatum((PLpgSQL_datum *) row);
!                         plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, row->rowno,
!                                            row->refname);
!
!                         arg_varnos[i] = row->rowno;
!                     }
!                     else
!                     {
!                         /*
!                          * Normal parameters get a var node
!                          */
!                         var = malloc(sizeof(PLpgSQL_var));
!                         memset(var, 0, sizeof(PLpgSQL_var));
!
!                         var->dtype = PLPGSQL_DTYPE_VAR;
!                         var->refname = strdup(buf);
!                         var->lineno = 0;
!                         var->datatype = build_datatype(typeTup, -1);
!                         var->isconst = true;
!                         var->notnull = false;
!                         var->default_val = NULL;
!
!                         plpgsql_adddatum((PLpgSQL_datum *) var);
!                         plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno,
!                                            var->refname);

!                         arg_varnos[i] = var->varno;
!                     }
!                     ReleaseSysCache(typeTup);
                  }
!                 break;

!             case T_TRIGGER:

!                 /*
!                  * Trigger procedures return type is unknown yet
!                  */
!                 function->fn_rettype = InvalidOid;
!                 function->fn_retbyval = false;
!                 function->fn_retistuple = true;
!                 function->fn_retset = false;

!                 /*
!                  * Add the record for referencing NEW
!                  */
!                 rec = malloc(sizeof(PLpgSQL_rec));
!                 memset(rec, 0, sizeof(PLpgSQL_rec));
!                 rec->dtype = PLPGSQL_DTYPE_REC;
!                 rec->refname = strdup("new");
!                 rec->tup = NULL;
!                 rec->tupdesc = NULL;
!                 rec->freetup = false;
!                 plpgsql_adddatum((PLpgSQL_datum *) rec);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
!                 function->new_varno = rec->recno;

!                 /*
!                  * Add the record for referencing OLD
!                  */
!                 rec = malloc(sizeof(PLpgSQL_rec));
!                 memset(rec, 0, sizeof(PLpgSQL_rec));
!                 rec->dtype = PLPGSQL_DTYPE_REC;
!                 rec->refname = strdup("old");
!                 rec->tup = NULL;
!                 rec->tupdesc = NULL;
!                 rec->freetup = false;
!                 plpgsql_adddatum((PLpgSQL_datum *) rec);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
!                 function->old_varno = rec->recno;

!                 /*
!                  * Add the variable tg_name
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_name");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("name");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_name_varno = var->varno;

!                 /*
!                  * Add the variable tg_when
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_when");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("text");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_when_varno = var->varno;

!                 /*
!                  * Add the variable tg_level
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_level");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("text");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_level_varno = var->varno;

!                 /*
!                  * Add the variable tg_op
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_op");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("text");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_op_varno = var->varno;

!                 /*
!                  * Add the variable tg_relid
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_relid");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("oid");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_relid_varno = var->varno;

!                 /*
!                  * Add the variable tg_relname
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_relname");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("name");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_relname_varno = var->varno;

!                 /*
!                  * Add the variable tg_nargs
!                  */
!                 var = malloc(sizeof(PLpgSQL_var));
!                 memset(var, 0, sizeof(PLpgSQL_var));

!                 var->dtype = PLPGSQL_DTYPE_VAR;
!                 var->refname = strdup("tg_nargs");
!                 var->lineno = 0;
!                 var->datatype = plpgsql_parse_datatype("int4");
!                 var->isconst = false;
!                 var->notnull = false;
!                 var->default_val = NULL;
!
!                 plpgsql_adddatum((PLpgSQL_datum *) var);
!                 plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!                 function->tg_nargs_varno = var->varno;

!                 break;

!             default:
!                 elog(ERROR, "unknown function type %u in plpgsql_compile()",
!                      functype);
!                 break;
!         }

!         /*
!          * Create the magic FOUND variable.
!          */
!         var = malloc(sizeof(PLpgSQL_var));
!         memset(var, 0, sizeof(PLpgSQL_var));

!         var->dtype = PLPGSQL_DTYPE_VAR;
!         var->refname = strdup("found");
!         var->lineno = 0;
!         var->datatype = plpgsql_parse_datatype("bool");
!         var->isconst = false;
!         var->notnull = false;
!         var->default_val = NULL;

!         plpgsql_adddatum((PLpgSQL_datum *) var);
!         plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
!         function->found_varno = var->varno;

!         /*
!          * Forget about the above created variables
!          */
!         plpgsql_add_initdatums(NULL);

!         /*
!          * Now parse the functions text
!          */
!         parse_rc = plpgsql_yyparse();
!         if (parse_rc != 0)
!             elog(ERROR, "plpgsql: parser returned %d ???", parse_rc);

!         plpgsql_scanner_finish();

!         /*
!          * If that was successful, complete the functions info.
!          */
!         function->fn_nargs = procStruct->pronargs;
!         for (i = 0; i < function->fn_nargs; i++)
!             function->fn_argvarnos[i] = arg_varnos[i];
!         function->ndatums = plpgsql_nDatums;
!         function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
!         for (i = 0; i < plpgsql_nDatums; i++)
!             function->datums[i] = plpgsql_Datums[i];
!         function->action = plpgsql_yylval.program;
!
!         /*
!          * add it to the hash table
!          */
!         plpgsql_HashTableInsert(function);
!
!         /*
!          * Pop the error context stack
!          */
!         error_context_stack = plerrcontext.previous;
!         plpgsql_error_funcname = NULL;
!         plpgsql_error_lineno = 0;
!     }

      ReleaseSysCache(procTup);

      /*
!      * Save pointer in FmgrInfo to avoid search on subsequent calls
       */
!     fcinfo->flinfo->fn_extra = (void *) function;

      /*
       * Finally return the compiled function
       */
      if (plpgsql_DumpExecTree)
          plpgsql_dumptree(function);
+
      return function;
  }

***************
*** 1500,1502 ****
--- 1595,1767 ----
      plpgsql_error_lineno = plpgsql_scanner_lineno();
      elog(ERROR, "%s at or near \"%s\"", s, plpgsql_yytext);
  }
+
+
+ /*
+  * get_function_by_signature
+  *
+  * Returns a function keyed by funcOid and argument types, or NULL
+  * if not found. Also checks to be sure what we found is still
+  * valid, clearing the hash table entry and returning NULL if it
+  * is out-of-date. This is needed because CREATE OR REPLACE FUNCTION
+  * can modify the function's pg_proc entry without changing its OID
+  * or arguments.
+  *
+  * Creates the hashtable if it doesn't already exist.
+  */
+ static PLpgSQL_function *
+ get_function_by_signature(FunctionCallInfo fcinfo, HeapTuple procTup)
+ {
+     PLpgSQL_function   *function = NULL;
+     PLpgSQL_func_key   *func_key;
+     Oid                    funcOid = fcinfo->flinfo->fn_oid;
+     Form_pg_proc        procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+     int                    i;
+     bool                entry_valid;
+
+     /*
+      * first see if we've already been here during the current
+      * SQL function call
+      */
+     function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
+
+     if (!function)
+     {
+         if (!plpgsql_HashTable)
+             plpgsql_HashTableInit();
+
+         func_key = (PLpgSQL_func_key *) palloc0(sizeof(PLpgSQL_func_key));
+         func_key->funcOid = funcOid;
+
+         /* get the argument types */
+         for (i = 0; i < procStruct->pronargs; i++)
+         {
+             Oid            argtypeid = InvalidOid;
+
+             /*
+              * Check for polymorphic arguments. If found, use the actual
+              * parameter type from the caller's FuncExpr node, if we
+              * have one.
+              */
+             if (procStruct->proargtypes[i] == ANYARRAYOID ||
+                 procStruct->proargtypes[i] == ANYELEMENTOID)
+             {
+                 argtypeid = get_fn_expr_argtype(fcinfo, i);
+                 if (!OidIsValid(argtypeid))
+                     elog(ERROR, "could not determine actual argument " \
+                                 "type for polymorphic function %s",
+                                  plpgsql_error_funcname);
+             }
+             else
+                 argtypeid = procStruct->proargtypes[i];
+
+             func_key->argtypes[i] = argtypeid;
+         }
+
+         function = plpgsql_HashTableLookup(func_key);
+     }
+
+     if (function)
+     {
+         /* we have a compiled function, but is it still valid */
+         entry_valid =
+             (function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
+              function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+
+         if (!entry_valid)
+         {
+             plpgsql_HashTableDelete(function);
+
+             /* XXX: is it worth worrying about the leaked function struct? */
+             function = NULL;
+         }
+     }
+
+     return function;
+ }
+
+ static void
+ plpgsql_HashTableInit(void)
+ {
+     HASHCTL        ctl;
+
+     memset(&ctl, 0, sizeof(ctl));
+     ctl.keysize = sizeof(PLpgSQL_func_key);
+     ctl.entrysize = sizeof(plpgsql_HashEnt);
+     ctl.hash = tag_hash;
+     plpgsql_HashTable = hash_create("PLpgSQL func cache", FUNCS_PER_USER,
+                                         &ctl, HASH_ELEM | HASH_FUNCTION);
+ }
+
+ static PLpgSQL_function *
+ plpgsql_HashTableLookup(PLpgSQL_func_key *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_HashEnt       *hentry;
+     bool                found;
+     PLpgSQL_func_key   *func_key;
+
+     func_key = function_get_func_key(function);
+     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;
+ }
+
+ static void
+ plpgsql_HashTableDelete(PLpgSQL_function *function)
+ {
+     plpgsql_HashEnt       *hentry;
+     PLpgSQL_func_key   *func_key;
+
+     func_key = function_get_func_key(function);
+     hentry = (plpgsql_HashEnt*) hash_search(plpgsql_HashTable,
+                                             (void *) func_key,
+                                             HASH_REMOVE,
+                                             NULL);
+
+     if (hentry == NULL)
+         elog(WARNING, "trying to delete function that does not exist.");
+ }
+
+ static PLpgSQL_func_key *
+ function_get_func_key(PLpgSQL_function *function)
+ {
+     PLpgSQL_func_key   *func_key;
+     int                    i;
+
+     Assert(function != NULL);
+
+     func_key = (PLpgSQL_func_key *) palloc0(sizeof(PLpgSQL_func_key));
+     func_key->funcOid = function->fn_oid;
+
+     /* get the argument types */
+     for (i = 0; i < function->fn_nargs; i++)
+         func_key->argtypes[i] = function->fn_argtypes[i];
+
+     return func_key;
+ }
+
Index: src/pl/plpgsql/src/pl_handler.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_handler.c,v
retrieving revision 1.12
diff -c -r1.12 pl_handler.c
*** src/pl/plpgsql/src/pl_handler.c    30 Aug 2002 00:28:41 -0000    1.12
--- src/pl/plpgsql/src/pl_handler.c    30 Jun 2003 21:35:11 -0000
***************
*** 44,59 ****
  #include "utils/builtins.h"
  #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
   *
--- 44,49 ----
***************
*** 67,76 ****
  Datum
  plpgsql_call_handler(PG_FUNCTION_ARGS)
  {
-     bool        isTrigger = CALLED_AS_TRIGGER(fcinfo);
-     Oid            funcOid = fcinfo->flinfo->fn_oid;
      PLpgSQL_function *func;
      Datum        retval;

      /*
       * Connect to SPI manager
--- 57,65 ----
  Datum
  plpgsql_call_handler(PG_FUNCTION_ARGS)
  {
      PLpgSQL_function *func;
      Datum        retval;
+     bool        isTrigger = CALLED_AS_TRIGGER(fcinfo);

      /*
       * Connect to SPI manager
***************
*** 78,126 ****
      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;
!     }

      /*
       * Determine if called as function or trigger and call appropriate
--- 67,74 ----
      if (SPI_connect() != SPI_OK_CONNECT)
          elog(ERROR, "plpgsql: cannot connect to SPI manager");

!     /* Find or compile the function */
!     func = plpgsql_compile(fcinfo);

      /*
       * Determine if called as function or trigger and call appropriate
***************
*** 139,169 ****
          elog(ERROR, "plpgsql: SPI_finish() failed");

      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;
  }
--- 87,90 ----
Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.36
diff -c -r1.36 plpgsql.h
*** src/pl/plpgsql/src/plpgsql.h    5 May 2003 16:46:28 -0000    1.36
--- src/pl/plpgsql/src/plpgsql.h    30 Jun 2003 21:28:42 -0000
***************
*** 504,509 ****
--- 504,510 ----
      bool        fn_retset;

      int            fn_nargs;
+     Oid            fn_argtypes[FUNC_MAX_ARGS];
      int            fn_argvarnos[FUNC_MAX_ARGS];
      int            found_varno;
      int            new_varno;
***************
*** 519,526 ****
      int            ndatums;
      PLpgSQL_datum **datums;
      PLpgSQL_stmt_block *action;
-
-     struct PLpgSQL_function *next;        /* for chaining list of functions */
  }    PLpgSQL_function;


--- 520,525 ----
***************
*** 588,594 ****
   * Functions in pl_comp.c
   * ----------
   */
! extern PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype);
  extern int    plpgsql_parse_word(char *word);
  extern int    plpgsql_parse_dblword(char *word);
  extern int    plpgsql_parse_tripword(char *word);
--- 587,593 ----
   * Functions in pl_comp.c
   * ----------
   */
! 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);

В списке pgsql-patches по дате отправления:

Предыдущее
От: Tom Lane
Дата:
Сообщение: Re: [HACKERS] Missing array support
Следующее
От: Joe Conway
Дата:
Сообщение: Re: polymorphic arguments and return type for PL/pgSQL