OSDN Git Service

plpgsql does OUT parameters, as per my proposal a few weeks ago.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 Apr 2005 06:22:17 +0000 (06:22 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 Apr 2005 06:22:17 +0000 (06:22 +0000)
doc/src/sgml/plpgsql.sgml
src/backend/utils/fmgr/funcapi.c
src/include/funcapi.h
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 77924fd..bd6f025 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.62 2005/03/13 09:36:30 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.63 2005/04/05 06:22:14 tgl Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -83,7 +83,7 @@ $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.62 2005/03/13 09:36:30 neilc Ex
     that contains many statements for which execution plans might be
     required will only prepare and save those plans that are really
     used during the lifetime of the database connection.  This can
-    substantially reduce the total amount of time required to parse,
+    substantially reduce the total amount of time required to parse
     and generate execution plans for the statements in a
     <application>PL/pgSQL</> function. A disadvantage is that errors
     in a specific expression or command may not be detected until that
@@ -215,6 +215,7 @@ $$ LANGUAGE plpgsql;
      <type>void</> if it has no useful return value.
     </para>
 
+    <note>
     <para>
      <application>PL/pgSQL</> does not currently have full support for
      domain types: it treats a domain the same as the underlying scalar
@@ -223,6 +224,20 @@ $$ LANGUAGE plpgsql;
      it is a hazard if you declare a <application>PL/pgSQL</> function
      as returning a domain type.
     </para>
+    </note>
+
+    <para>
+     <application>PL/pgSQL</> functions can also be declared with output
+     parameters in place of an explicit specification of the return type.
+     This does not add any fundamental capability to the language, but
+     it is often convenient, especially for returning multiple values.
+    </para>
+
+    <para>
+     Specific examples appear in
+     <xref linkend="plpgsql-declaration-aliases"> and
+     <xref linkend="plpgsql-statements-returning">.
+    </para>
   </sect2>
  </sect1>
 
@@ -631,12 +646,12 @@ DECLARE
     v_string ALIAS FOR $1;
     index ALIAS FOR $2;
 BEGIN
-    -- some computations here
+    -- some computations using v_string and index here
 END;
 $$ LANGUAGE plpgsql;
 
 
-CREATE FUNCTION concat_selected_fields(in_t tablename) RETURNS text AS $$
+CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
 BEGIN
     RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
 END;
@@ -645,6 +660,49 @@ $$ LANGUAGE plpgsql;
      </para>
 
      <para>
+      When a <application>PL/pgSQL</application> function is declared
+      with output parameters, the output parameters are given
+      <literal>$<replaceable>n</replaceable></literal> names and optional
+      aliases in just the same way as the normal input parameters.  An
+      output parameter is effectively a variable that starts out NULL;
+      it should be assigned to during the execution of the function.
+      The final value of the parameter is what is returned.  For instance,
+      the sales-tax example could also be done this way:
+
+<programlisting>
+CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
+BEGIN
+    tax := subtotal * 0.06;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+
+      Notice that we omitted <literal>RETURNS real</> &mdash; we could have
+      included it, but it would be redundant.
+     </para>
+
+     <para>
+      Output parameters are most useful when returning multiple values.
+      A trivial example is:
+
+<programlisting>
+CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
+BEGIN
+    sum := x + y;
+    prod := x * y;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+
+      As discussed in <xref linkend="xfunc-output-parameters">, this
+      effectively creates an anonymous record type for the function's
+      results.  If a <literal>RETURNS</> clause is given, it must say
+      <literal>RETURNS record</>.
+     </para>
+
+     <para>
       When the return type of a <application>PL/pgSQL</application>
       function is declared as a polymorphic type (<type>anyelement</type>
       or <type>anyarray</type>), a special parameter <literal>$0</literal>
@@ -658,6 +716,7 @@ $$ LANGUAGE plpgsql;
       though that is not required.  <literal>$0</literal> can also be
       given an alias.  For example, this function works on any data type
       that has a <literal>+</> operator:
+
 <programlisting>
 CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
 RETURNS anyelement AS $$
@@ -670,6 +729,24 @@ END;
 $$ LANGUAGE plpgsql;
 </programlisting>
      </para>
+
+     <para>
+      The same effect can be had by declaring one or more output parameters as
+      <type>anyelement</type> or <type>anyarray</type>.  In this case the
+      special <literal>$0</literal> parameter is not used; the output
+      parameters themselves serve the same purpose.  For example:
+
+<programlisting>
+CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
+                                 OUT sum anyelement)
+AS $$
+BEGIN
+    sum := v1 + v2 + v3;
+    RETURN;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+     </para>
     </sect2>
 
   <sect2 id="plpgsql-declaration-type">
@@ -756,18 +833,21 @@ user_id users.user_id%TYPE;
    </para>
 
    <para>
-    Here is an example of using composite types:
+    Here is an example of using composite types.  <structname>table1</>
+    and <structname>table2</> are existing tables having at least the
+    mentioned fields:
+
 <programlisting>
-CREATE FUNCTION merge_fields(t_row tablename) RETURNS text AS $$
+CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
 DECLARE
-    t2_row table2name%ROWTYPE;
+    t2_row table2%ROWTYPE;
 BEGIN
-    SELECT * INTO t2_row FROM table2name WHERE ... ;
+    SELECT * INTO t2_row FROM table2 WHERE ... ;
     RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
 END;
 $$ LANGUAGE plpgsql;
 
-SELECT merge_fields(t.*) FROM tablename t WHERE ... ;
+SELECT merge_fields(t.*) FROM table1 t WHERE ... ;
 </programlisting>
    </para>
   </sect2>
@@ -1412,6 +1492,12 @@ RETURN <replaceable>expression</replaceable>;
      </para>
 
      <para>
+      If you declared the function with output parameters, write just
+      <command>RETURN</command> with no expression.  The current values
+      of the output parameter variables will be returned.
+     </para>
+
+     <para>
       The return value of a function cannot be left undefined. If
       control reaches the end of the top-level block of the function
       without hitting a <command>RETURN</command> statement, a run-time
@@ -1441,8 +1527,30 @@ RETURN NEXT <replaceable>expression</replaceable>;
       commands, and then a final <command>RETURN</command> command
       with no argument is used to indicate that the function has
       finished executing.  <command>RETURN NEXT</command> can be used
-      with both scalar and composite data types; in the latter case, an
-      entire <quote>table</quote> of results will be returned.
+      with both scalar and composite data types; with a composite result
+      type, an entire <quote>table</quote> of results will be returned.
+     </para>
+
+     <para>
+      <command>RETURN NEXT</command> does not actually return from the
+      function &mdash; it simply saves away the value of the expression.
+      Execution then continues with the next statement in
+      the <application>PL/pgSQL</> function.  As successive
+      <command>RETURN NEXT</command> commands are executed, the result
+      set is built up.  A final <command>RETURN</command>, which should
+      have no argument, causes control to exit the function.
+     </para>
+
+     <para>
+      If you declared the function with output parameters, write just
+      <command>RETURN NEXT</command> with no expression.  The current values
+      of the output parameter variable(s) will be saved for eventual return.
+      Note that you must declare the function as returning
+      <literal>SETOF record</literal> when there are
+      multiple output parameters, or
+      <literal>SETOF <replaceable>sometype</></literal> when there is
+      just one output parameter of type <replaceable>sometype</>, in
+      order to create a set-returning function with output parameters.
      </para>
 
      <para>
@@ -1457,16 +1565,6 @@ SELECT * FROM some_func();
       <literal>FROM</literal> clause.
      </para>
 
-     <para>
-      <command>RETURN NEXT</command> does not actually return from the
-      function; it simply saves away the value of the expression.
-      Execution then continues with the next statement in
-      the <application>PL/pgSQL</> function.  As successive
-      <command>RETURN NEXT</command> commands are executed, the result
-      set is built up.  A final <command>RETURN</command>, which should
-      have no argument, causes control to exit the function.
-     </para>
-
      <note>
       <para>
        The current implementation of <command>RETURN NEXT</command>
index 160847d..b16cbf9 100644 (file)
@@ -7,7 +7,7 @@
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.20 2005/04/05 06:22:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -484,6 +484,108 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 }
 
 /*
+ * Given the declared argument types and modes for a function,
+ * replace any polymorphic types (ANYELEMENT/ANYARRAY) with correct data
+ * types deduced from the input arguments.  Returns TRUE if able to deduce
+ * all types, FALSE if not.  This is the same logic as
+ * resolve_polymorphic_tupdesc, but with a different argument representation.
+ *
+ * argmodes may be NULL, in which case all arguments are assumed to be IN mode.
+ */
+bool
+resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
+                                                        Node *call_expr)
+{
+       bool            have_anyelement_result = false;
+       bool            have_anyarray_result = false;
+       Oid                     anyelement_type = InvalidOid;
+       Oid                     anyarray_type = InvalidOid;
+       int                     inargno;
+       int                     i;
+
+       /* First pass: resolve polymorphic inputs, check for outputs */
+       inargno = 0;
+       for (i = 0; i < numargs; i++)
+       {
+               char    argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+               switch (argtypes[i])
+               {
+                       case ANYELEMENTOID:
+                               if (argmode == PROARGMODE_OUT)
+                                       have_anyelement_result = true;
+                               else
+                               {
+                                       if (!OidIsValid(anyelement_type))
+                                       {
+                                               anyelement_type = get_call_expr_argtype(call_expr,
+                                                                                                                               inargno);
+                                               if (!OidIsValid(anyelement_type))
+                                                       return false;
+                                       }
+                                       argtypes[i] = anyelement_type;
+                               }
+                               break;
+                       case ANYARRAYOID:
+                               if (argmode == PROARGMODE_OUT)
+                                       have_anyarray_result = true;
+                               else
+                               {
+                                       if (!OidIsValid(anyarray_type))
+                                       {
+                                               anyarray_type = get_call_expr_argtype(call_expr,
+                                                                                                                         inargno);
+                                               if (!OidIsValid(anyarray_type))
+                                                       return false;
+                                       }
+                                       argtypes[i] = anyarray_type;
+                               }
+                               break;
+                       default:
+                               break;
+               }
+               if (argmode != PROARGMODE_OUT)
+                       inargno++;
+       }
+
+       /* Done? */
+       if (!have_anyelement_result && !have_anyarray_result)
+               return true;
+
+       /* If no input polymorphics, parser messed up */
+       if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type))
+               return false;
+
+       /* If needed, deduce one polymorphic type from the other */
+       if (have_anyelement_result && !OidIsValid(anyelement_type))
+               anyelement_type = resolve_generic_type(ANYELEMENTOID,
+                                                                                          anyarray_type,
+                                                                                          ANYARRAYOID);
+       if (have_anyarray_result && !OidIsValid(anyarray_type))
+               anyarray_type = resolve_generic_type(ANYARRAYOID,
+                                                                                        anyelement_type,
+                                                                                        ANYELEMENTOID);
+
+       /* And finally replace the output column types as needed */
+       for (i = 0; i < numargs; i++)
+       {
+               switch (argtypes[i])
+               {
+                       case ANYELEMENTOID:
+                               argtypes[i] = anyelement_type;
+                               break;
+                       case ANYARRAYOID:
+                               argtypes[i] = anyarray_type;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       return true;
+}
+
+/*
  * get_type_func_class
  *             Given the type OID, obtain its TYPEFUNC classification.
  *
index c4d1809..2b4bfa2 100644 (file)
@@ -9,7 +9,7 @@
  *
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.17 2005/04/05 06:22:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -167,6 +167,10 @@ extern TypeFuncClass get_func_result_type(Oid functionId,
                                                                                  Oid *resultTypeId,
                                                                                  TupleDesc *resultTupleDesc);
 
+extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes,
+                                                                                char *argmodes,
+                                                                                Node *call_expr);
+
 extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
                                                                                                 Datum proargmodes,
                                                                                                 Datum proargnames);
index 210d144..e2b5c7a 100644 (file)
@@ -4,7 +4,7 @@
  *                                               procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.66 2005/02/22 07:18:24 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.67 2005/04/05 06:22:16 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -1052,28 +1052,41 @@ stmt_return             : K_RETURN lno
                                                PLpgSQL_stmt_return *new;
 
                                                new = palloc0(sizeof(PLpgSQL_stmt_return));
+                                               new->cmd_type = PLPGSQL_STMT_RETURN;
+                                               new->lineno   = $2;
                                                new->expr = NULL;
-                                               new->retrecno   = -1;
-                                               new->retrowno   = -1;
+                                               new->retvarno   = -1;
 
                                                if (plpgsql_curr_compile->fn_retset)
                                                {
                                                        if (yylex() != ';')
                                                                yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT");
                                                }
+                                               else if (plpgsql_curr_compile->out_param_varno >= 0)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN cannot have a parameter in function with OUT parameters");
+                                                       new->retvarno = plpgsql_curr_compile->out_param_varno;
+                                               }
+                                               else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
+                                               {
+                                                       if (yylex() != ';')
+                                                               yyerror("function returning void cannot specify RETURN expression");
+                                               }
                                                else if (plpgsql_curr_compile->fn_retistuple)
                                                {
                                                        switch (yylex())
                                                        {
                                                                case K_NULL:
+                                                                       /* we allow this to support RETURN NULL in triggers */
                                                                        break;
 
                                                                case T_ROW:
-                                                                       new->retrowno = yylval.row->rowno;
+                                                                       new->retvarno = yylval.row->rowno;
                                                                        break;
 
                                                                case T_RECORD:
-                                                                       new->retrecno = yylval.rec->recno;
+                                                                       new->retvarno = yylval.rec->recno;
                                                                        break;
 
                                                                default:
@@ -1083,11 +1096,6 @@ stmt_return              : K_RETURN lno
                                                        if (yylex() != ';')
                                                                yyerror("RETURN must specify a record or row variable in function returning tuple");
                                                }
-                                               else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
-                                               {
-                                                       if (yylex() != ';')
-                                                               yyerror("function returning void cannot specify RETURN expression");
-                                               }
                                                else
                                                {
                                                        /*
@@ -1098,9 +1106,6 @@ stmt_return               : K_RETURN lno
                                                        new->expr = plpgsql_read_expression(';', ";");
                                                }
 
-                                               new->cmd_type = PLPGSQL_STMT_RETURN;
-                                               new->lineno   = $2;
-
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
@@ -1115,18 +1120,31 @@ stmt_return_next: K_RETURN_NEXT lno
                                                new = palloc0(sizeof(PLpgSQL_stmt_return_next));
                                                new->cmd_type   = PLPGSQL_STMT_RETURN_NEXT;
                                                new->lineno             = $2;
+                                               new->expr = NULL;
+                                               new->retvarno   = -1;
 
-                                               if (plpgsql_curr_compile->fn_retistuple)
+                                               if (plpgsql_curr_compile->out_param_varno >= 0)
                                                {
-                                                       int tok = yylex();
+                                                       if (yylex() != ';')
+                                                               yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters");
+                                                       new->retvarno = plpgsql_curr_compile->out_param_varno;
+                                               }
+                                               else if (plpgsql_curr_compile->fn_retistuple)
+                                               {
+                                                       switch (yylex())
+                                                       {
+                                                               case T_ROW:
+                                                                       new->retvarno = yylval.row->rowno;
+                                                                       break;
 
-                                                       if (tok == T_RECORD)
-                                                               new->rec = yylval.rec;
-                                                       else if (tok == T_ROW)
-                                                               new->row = yylval.row;
-                                                       else
-                                                               yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
+                                                               case T_RECORD:
+                                                                       new->retvarno = yylval.rec->recno;
+                                                                       break;
 
+                                                               default:
+                                                                       yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
+                                                                       break;
+                                                       }
                                                        if (yylex() != ';')
                                                                yyerror("RETURN NEXT must specify a record or row variable in function returning tuple");
                                                }
index 4d23062..23109c9 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.85 2005/03/29 00:17:23 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.86 2005/04/05 06:22:16 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -49,6 +49,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "parser/gramparse.h"
 #include "parser/parse_type.h"
@@ -122,13 +123,20 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
                   HeapTuple procTup,
                   PLpgSQL_func_hashkey *hashkey,
                   bool forValidator);
-static char **fetchArgNames(HeapTuple procTup, int nargs);
-static PLpgSQL_row *build_row_var(Oid classOid);
+static int     fetchArgInfo(HeapTuple procTup,
+                                                Oid **p_argtypes, char ***p_argnames,
+                                                char **p_argmodes);
+static PLpgSQL_row *build_row_from_class(Oid classOid);
+static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
 static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
 static void compute_function_hashkey(FunctionCallInfo fcinfo,
                                                 Form_pg_proc procStruct,
                                                 PLpgSQL_func_hashkey *hashkey,
                                                 bool forValidator);
+static void plpgsql_resolve_polymorphic_argtypes(int numargs,
+                                                                        Oid *argtypes, char *argmodes,
+                                                                        Node *call_expr, bool forValidator,
+                                                                        const char *proname);
 static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
 static void plpgsql_HashTableInsert(PLpgSQL_function *function,
                                                PLpgSQL_func_hashkey *func_key);
@@ -259,11 +267,17 @@ do_compile(FunctionCallInfo fcinfo,
        PLpgSQL_variable *var;
        PLpgSQL_rec *rec;
        int                     i;
-       int                     arg_varnos[FUNC_MAX_ARGS];
        ErrorContextCallback plerrcontext;
        int                     parse_rc;
        Oid                     rettypeid;
+       int                     numargs;
+       int                     num_in_args;
+       int                     num_out_args;
+       Oid                *argtypes;
        char      **argnames;
+       char       *argmodes;
+       int                *in_arg_varnos = NULL;
+       PLpgSQL_variable **out_arg_variables;
        MemoryContext func_cxt;
 
        /*
@@ -330,11 +344,111 @@ do_compile(FunctionCallInfo fcinfo,
        function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
        function->fn_functype = functype;
        function->fn_cxt = func_cxt;
+       function->out_param_varno = -1;                 /* set up for no OUT param */
 
        switch (functype)
        {
                case T_FUNCTION:
                        /*
+                        * Fetch info about the procedure's parameters. Allocations
+                        * aren't needed permanently, so make them in tmp cxt.
+                        *
+                        * We also need to resolve any polymorphic input or output
+                        * argument types.  In validation mode we won't be able to,
+                        * so we arbitrarily assume we are dealing with integers.
+                        */
+                       MemoryContextSwitchTo(compile_tmp_cxt);
+
+                       numargs = fetchArgInfo(procTup, &argtypes, &argnames, &argmodes);
+
+                       plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+                                                                                                fcinfo->flinfo->fn_expr,
+                                                                                                forValidator,
+                                                                                                plpgsql_error_funcname);
+
+                       in_arg_varnos = (int *) palloc(numargs * sizeof(int));
+                       out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *));
+
+                       MemoryContextSwitchTo(func_cxt);
+
+                       /*
+                        * Create the variables for the procedure's parameters.
+                        */
+                       num_in_args = num_out_args = 0;
+                       for (i = 0; i < numargs; i++)
+                       {
+                               char            buf[32];
+                               Oid                     argtypeid = argtypes[i];
+                               char            argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+                               PLpgSQL_type *argdtype;
+                               PLpgSQL_variable *argvariable;
+                               int                     argitemtype;
+
+                               /* Create $n name for variable */
+                               snprintf(buf, sizeof(buf), "$%d", i + 1);
+
+                               /* Create datatype info */
+                               argdtype = plpgsql_build_datatype(argtypeid, -1);
+
+                               /* Disallow pseudotype argument */
+                               /* (note we already replaced ANYARRAY/ANYELEMENT) */
+                               /* (build_variable would do this, but wrong message) */
+                               if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR &&
+                                       argdtype->ttype != PLPGSQL_TTYPE_ROW)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                 errmsg("plpgsql functions cannot take type %s",
+                                                                format_type_be(argtypeid))));
+
+                               /* Build variable and add to datum list */
+                               argvariable = plpgsql_build_variable(buf, 0,
+                                                                                                        argdtype, false);
+
+                               if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
+                               {
+                                       argitemtype = PLPGSQL_NSTYPE_VAR;
+                                       /* input argument vars are forced to be CONSTANT */
+                                       if (argmode == PROARGMODE_IN)
+                                               ((PLpgSQL_var *) argvariable)->isconst = true;
+                               }
+                               else
+                               {
+                                       Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW);
+                                       argitemtype = PLPGSQL_NSTYPE_ROW;
+                               }
+
+                               /* Remember arguments in appropriate arrays */
+                               if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT)
+                                       in_arg_varnos[num_in_args++] = argvariable->dno;
+                               if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT)
+                                       out_arg_variables[num_out_args++] = argvariable;
+
+                               /* Add to namespace under the $n name */
+                               plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
+
+                               /* If there's a name for the argument, make an alias */
+                               if (argnames && argnames[i][0] != '\0')
+                                       plpgsql_ns_additem(argitemtype, argvariable->dno,
+                                                                          argnames[i]);
+                       }
+
+                       /*
+                        * If there's just one OUT parameter, out_param_varno points
+                        * directly to it.  If there's more than one, build a row
+                        * that holds all of them.
+                        */
+                       if (num_out_args == 1)
+                               function->out_param_varno = out_arg_variables[0]->dno;
+                       else if (num_out_args > 1)
+                       {
+                               PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
+                                                                                                          num_out_args);
+
+                               plpgsql_adddatum((PLpgSQL_datum *) row);
+                               function->out_param_varno = row->rowno;
+                       }
+
+                       /*
                         * Check for a polymorphic returntype. If found, use the
                         * actual returntype type from the caller's FuncExpr node, if
                         * we have one.  (In validation mode we arbitrarily assume we
@@ -355,13 +469,15 @@ do_compile(FunctionCallInfo fcinfo,
                                                rettypeid = INT4OID;
                                }
                                else
+                               {
                                        rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
-                               if (!OidIsValid(rettypeid))
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("could not determine actual return type "
-                                                               "for polymorphic function \"%s\"",
-                                                               plpgsql_error_funcname)));
+                                       if (!OidIsValid(rettypeid))
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                errmsg("could not determine actual return type "
+                                                                               "for polymorphic function \"%s\"",
+                                                                               plpgsql_error_funcname)));
+                               }
                        }
 
                        /*
@@ -410,10 +526,12 @@ do_compile(FunctionCallInfo fcinfo,
 
                                /*
                                 * install $0 reference, but only for polymorphic return
-                                * types
+                                * types, and not when the return is specified through an
+                                * output parameter.
                                 */
-                               if (procStruct->prorettype == ANYARRAYOID ||
-                                       procStruct->prorettype == ANYELEMENTOID)
+                               if ((procStruct->prorettype == ANYARRAYOID ||
+                                        procStruct->prorettype == ANYELEMENTOID) &&
+                                       num_out_args == 0)
                                {
                                        (void) plpgsql_build_variable("$0", 0,
                                                                                         build_datatype(typeTup, -1),
@@ -421,72 +539,6 @@ do_compile(FunctionCallInfo fcinfo,
                                }
                        }
                        ReleaseSysCache(typeTup);
-
-                       /*
-                        * Create the variables for the procedure's
-                        * parameters. Allocations aren't needed permanently, so
-                        * make them in tmp cxt.
-                        */
-                       MemoryContextSwitchTo(compile_tmp_cxt);
-                       argnames = fetchArgNames(procTup, procStruct->pronargs);
-                       MemoryContextSwitchTo(func_cxt);
-
-                       for (i = 0; i < procStruct->pronargs; i++)
-                       {
-                               char            buf[32];
-                               Oid                     argtypeid;
-                               PLpgSQL_type *argdtype;
-                               PLpgSQL_variable *argvariable;
-                               int                     argitemtype;
-
-                               /* Create $n name for variable */
-                               snprintf(buf, sizeof(buf), "$%d", i + 1);
-
-                               /*
-                                * Since we already did the replacement of polymorphic
-                                * argument types by actual argument types while computing
-                                * the hashkey, we can just use those results.
-                                */
-                               argtypeid = hashkey->argtypes[i];
-                               argdtype = plpgsql_build_datatype(argtypeid, -1);
-
-                               /* Disallow pseudotype argument */
-                               /* (note we already replaced ANYARRAY/ANYELEMENT) */
-                               /* (build_variable would do this, but wrong message) */
-                               if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR &&
-                                       argdtype->ttype != PLPGSQL_TTYPE_ROW)
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                 errmsg("plpgsql functions cannot take type %s",
-                                                                format_type_be(argtypeid))));
-
-                               /* Build variable and add to datum list */
-                               argvariable = plpgsql_build_variable(buf, 0,
-                                                                                                        argdtype, false);
-
-                               if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
-                               {
-                                       /* argument vars are forced to be CONSTANT (why?) */
-                                       ((PLpgSQL_var *) argvariable)->isconst = true;
-                                       argitemtype = PLPGSQL_NSTYPE_VAR;
-                               }
-                               else
-                               {
-                                       Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW);
-                                       argitemtype = PLPGSQL_NSTYPE_ROW;
-                               }
-
-                               /* Remember datum number */
-                               arg_varnos[i] = argvariable->dno;
-
-                               /* Add to namespace under the $n name */
-                               plpgsql_ns_additem(argitemtype, argvariable->dno, buf);
-
-                               /* If there's a name for the argument, make an alias */
-                               if (argnames)
-                                       plpgsql_ns_additem(argitemtype, argvariable->dno,
-                                                                          argnames[i]);
-                       }
                        break;
 
                case T_TRIGGER:
@@ -598,7 +650,7 @@ do_compile(FunctionCallInfo fcinfo,
         */
        function->fn_nargs = procStruct->pronargs;
        for (i = 0; i < function->fn_nargs; i++)
-               function->fn_argvarnos[i] = arg_varnos[i];
+               function->fn_argvarnos[i] = in_arg_varnos[i];
        function->ndatums = plpgsql_nDatums;
        function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
        for (i = 0; i < plpgsql_nDatums; i++)
@@ -660,40 +712,96 @@ plpgsql_compile_error_callback(void *arg)
 
 
 /*
- * Fetch the argument names, if any, from the proargnames field of the
- * pg_proc tuple.  Results are palloc'd.
+ * Fetch info about the argument types, names, and IN/OUT modes from the
+ * pg_proc tuple.  Return value is the number of arguments.
+ * Other results are palloc'd.
  */
-static char **
-fetchArgNames(HeapTuple procTup, int nargs)
+static int
+fetchArgInfo(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames,
+                        char **p_argmodes)
 {
-       Datum           argnamesDatum;
+       Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+       Datum           proallargtypes;
+       Datum           proargmodes;
+       Datum           proargnames;
        bool            isNull;
+       ArrayType  *arr;
+       int                     numargs;
        Datum      *elems;
        int                     nelems;
-       char      **result;
        int                     i;
 
-       if (nargs == 0)
-               return NULL;
+       /* First discover the total number of parameters and get their types */
+       proallargtypes = SysCacheGetAttr(PROCOID, procTup,
+                                                                        Anum_pg_proc_proallargtypes,
+                                                                        &isNull);
+       if (!isNull)
+       {
+               /*
+                * We expect the arrays to be 1-D arrays of the right types; verify
+                * that.  For the OID and char arrays, we don't need to use
+                * deconstruct_array() since the array data is just going to look like
+                * a C array of values.
+                */
+               arr = DatumGetArrayTypeP(proallargtypes);       /* ensure not toasted */
+               numargs = ARR_DIMS(arr)[0];
+               if (ARR_NDIM(arr) != 1 ||
+                       numargs < 0 ||
+                       ARR_ELEMTYPE(arr) != OIDOID)
+                       elog(ERROR, "proallargtypes is not a 1-D Oid array");
+               Assert(numargs >= procStruct->pronargs);
+               *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+               memcpy(*p_argtypes, ARR_DATA_PTR(arr),
+                          numargs * sizeof(Oid));
+       }
+       else
+       {
+               /* If no proallargtypes, use proargtypes */
+               numargs = procStruct->proargtypes.dim1;
+               Assert(numargs == procStruct->pronargs);
+               *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid));
+               memcpy(*p_argtypes, procStruct->proargtypes.values,
+                          numargs * sizeof(Oid));
+       }
 
-       argnamesDatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames,
-                                                                       &isNull);
+       /* Get argument names, if available */
+       proargnames = SysCacheGetAttr(PROCOID, procTup,
+                                                                 Anum_pg_proc_proargnames,
+                                                                 &isNull);
        if (isNull)
-               return NULL;
-
-       deconstruct_array(DatumGetArrayTypeP(argnamesDatum),
-                                         TEXTOID, -1, false, 'i',
-                                         &elems, &nelems);
-
-       if (nelems != nargs)            /* should not happen */
-               elog(ERROR, "proargnames must have the same number of elements as the function has arguments");
-
-       result = (char **) palloc(sizeof(char *) * nargs);
+               *p_argnames = NULL;
+       else
+       {
+               deconstruct_array(DatumGetArrayTypeP(proargnames),
+                                                 TEXTOID, -1, false, 'i',
+                                                 &elems, &nelems);
+               if (nelems != numargs)          /* should not happen */
+                       elog(ERROR, "proargnames must have the same number of elements as the function has arguments");
+               *p_argnames = (char **) palloc(sizeof(char *) * numargs);
+               for (i = 0; i < numargs; i++)
+                       (*p_argnames)[i] = DatumGetCString(DirectFunctionCall1(textout,
+                                                                                                                                  elems[i]));
+       }
 
-       for (i = 0; i < nargs; i++)
-               result[i] = DatumGetCString(DirectFunctionCall1(textout, elems[i]));
+       /* Get argument modes, if available */
+       proargmodes = SysCacheGetAttr(PROCOID, procTup,
+                                                                 Anum_pg_proc_proargmodes,
+                                                                 &isNull);
+       if (isNull)
+               *p_argmodes = NULL;
+       else
+       {
+               arr = DatumGetArrayTypeP(proargmodes);  /* ensure not toasted */
+               if (ARR_NDIM(arr) != 1 ||
+                       ARR_DIMS(arr)[0] != numargs ||
+                       ARR_ELEMTYPE(arr) != CHAROID)
+                       elog(ERROR, "proargmodes is not a 1-D char array");
+               *p_argmodes = (char *) palloc(numargs * sizeof(char));
+               memcpy(*p_argmodes, ARR_DATA_PTR(arr),
+                          numargs * sizeof(char));
+       }
 
-       return result;
+       return numargs;
 }
 
 
@@ -1449,7 +1557,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
                                /* Composite type -- build a row variable */
                                PLpgSQL_row *row;
 
-                               row = build_row_var(dtype->typrelid);
+                               row = build_row_from_class(dtype->typrelid);
 
                                row->dtype = PLPGSQL_DTYPE_ROW;
                                row->refname = pstrdup(refname);
@@ -1504,7 +1612,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
  * Build a row-variable data structure given the pg_class OID.
  */
 static PLpgSQL_row *
-build_row_var(Oid classOid)
+build_row_from_class(Oid classOid)
 {
        PLpgSQL_row *row;
        Relation        rel;
@@ -1589,6 +1697,62 @@ build_row_var(Oid classOid)
        return row;
 }
 
+/*
+ * Build a row-variable data structure given the component variables.
+ */
+static PLpgSQL_row *
+build_row_from_vars(PLpgSQL_variable **vars, int numvars)
+{
+       PLpgSQL_row *row;
+       int                     i;
+
+       row = palloc0(sizeof(PLpgSQL_row));
+       row->dtype = PLPGSQL_DTYPE_ROW;
+       row->rowtupdesc = CreateTemplateTupleDesc(numvars, false);
+       row->nfields = numvars;
+       row->fieldnames = palloc(numvars * sizeof(char *));
+       row->varnos = palloc(numvars * sizeof(int));
+
+       for (i = 0; i < numvars; i++)
+       {
+               PLpgSQL_variable *var = vars[i];
+               Oid             typoid = RECORDOID;
+               int32   typmod = -1;
+
+               switch (var->dtype)
+               {
+                       case PLPGSQL_DTYPE_VAR:
+                               typoid = ((PLpgSQL_var *) var)->datatype->typoid;
+                               typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
+                               break;
+
+                       case PLPGSQL_DTYPE_REC:
+                               break;
+
+                       case PLPGSQL_DTYPE_ROW:
+                               if (((PLpgSQL_row *) var)->rowtupdesc)
+                               {
+                                       typoid = ((PLpgSQL_row *) var)->rowtupdesc->tdtypeid;
+                                       typmod = ((PLpgSQL_row *) var)->rowtupdesc->tdtypmod;
+                               }
+                               break;
+
+                       default:
+                               elog(ERROR, "unrecognized dtype: %d", var->dtype);
+               }
+
+               row->fieldnames[i] = var->refname;
+               row->varnos[i] = var->dno;
+
+               TupleDescInitEntry(row->rowtupdesc, i+1,
+                                                  var->refname,
+                                                  typoid, typmod,
+                                                  0);
+       }
+
+       return row;
+}
+
 
 /* ----------
  * plpgsql_parse_datatype                      Scanner found something that should
@@ -1820,8 +1984,6 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
                                                 PLpgSQL_func_hashkey *hashkey,
                                                 bool forValidator)
 {
-       int                     i;
-
        /* Make sure any unused bytes of the struct are zero */
        MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
 
@@ -1840,42 +2002,64 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
                hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation);
        }
 
-       /* get the argument types */
-       for (i = 0; i < procStruct->pronargs; i++)
+       if (procStruct->pronargs > 0)
        {
-               Oid                     argtypeid = procStruct->proargtypes.values[i];
+               /* get the argument types */
+               memcpy(hashkey->argtypes, procStruct->proargtypes.values,
+                          procStruct->pronargs * sizeof(Oid));
+
+               /* resolve any polymorphic argument types */
+               plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs,
+                                                                                        hashkey->argtypes,
+                                                                                        NULL,
+                                                                                        fcinfo->flinfo->fn_expr,
+                                                                                        forValidator,
+                                                                                        NameStr(procStruct->proname));
+       }
+}
 
-               /*
-                * Check for polymorphic arguments. If found, use the actual
-                * parameter type from the caller's FuncExpr node, if we have one.
-                * (In validation mode we arbitrarily assume we are dealing with
-                * integers.  This lets us build a valid, if possibly useless,
-                * function hashtable entry.)
-                *
-                * We can support arguments of type ANY the same way as normal
-                * polymorphic arguments.
-                */
-               if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
-                       argtypeid == ANYOID)
+/*
+ * This is the same as the standard resolve_polymorphic_argtypes() function,
+ * but with a special case for validation: assume that polymorphic arguments
+ * are integer or integer-array.  Also, we go ahead and report the error
+ * if we can't resolve the types.
+ */
+static void
+plpgsql_resolve_polymorphic_argtypes(int numargs,
+                                                                        Oid *argtypes, char *argmodes,
+                                                                        Node *call_expr, bool forValidator,
+                                                                        const char *proname)
+{
+       int                     i;
+
+       if (!forValidator)
+       {
+               /* normal case, pass to standard routine */
+               if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+                                                                                 call_expr))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("could not determine actual argument "
+                                                       "type for polymorphic function \"%s\"",
+                                                       proname)));
+       }
+       else
+       {
+               /* special validation case */
+               for (i = 0; i < numargs; i++)
                {
-                       if (forValidator)
+                       switch (argtypes[i])
                        {
-                               if (argtypeid == ANYARRAYOID)
-                                       argtypeid = INT4ARRAYOID;
-                               else
-                                       argtypeid = INT4OID;
+                               case ANYELEMENTOID:
+                                       argtypes[i] = INT4OID;
+                                       break;
+                               case ANYARRAYOID:
+                                       argtypes[i] = INT4ARRAYOID;
+                                       break;
+                               default:
+                                       break;
                        }
-                       else
-                               argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
-                       if (!OidIsValid(argtypeid))
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("could not determine actual argument "
-                                                               "type for polymorphic function \"%s\"",
-                                                               NameStr(procStruct->proname))));
                }
-
-               hashkey->argtypes[i] = argtypeid;
        }
 }
 
index 9fbe289..4454f28 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.133 2005/03/25 01:45:42 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.134 2005/04/05 06:22:16 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -75,8 +75,6 @@ static PLpgSQL_expr *active_simple_exprs = NULL;
  ************************************************************/
 static void plpgsql_exec_error_callback(void *arg);
 static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
-static PLpgSQL_var *copy_var(PLpgSQL_var *var);
-static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec);
 
 static int exec_stmt_block(PLpgSQL_execstate *estate,
                                PLpgSQL_stmt_block *block);
@@ -212,11 +210,11 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
         * Make local execution copies of all the datums
         */
        estate.err_text = gettext_noop("during initialization of execution state");
-       for (i = 0; i < func->ndatums; i++)
+       for (i = 0; i < estate.ndatums; i++)
                estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
 
        /*
-        * Store the actual call argument values into the variables
+        * Store the actual call argument values into the appropriate variables
         */
        estate.err_text = gettext_noop("while storing call arguments into local variables");
        for (i = 0; i < func->fn_nargs; i++)
@@ -273,36 +271,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
        }
 
        /*
-        * Initialize the other variables to NULL values for now. The default
-        * values are set when the blocks are entered.
-        */
-       estate.err_text = gettext_noop("while initializing local variables to NULL");
-       for (i = estate.found_varno; i < estate.ndatums; i++)
-       {
-               switch (estate.datums[i]->dtype)
-               {
-                       case PLPGSQL_DTYPE_VAR:
-                               {
-                                       PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
-
-                                       var->value = 0;
-                                       var->isnull = true;
-                                       var->freeval = false;
-                               }
-                               break;
-
-                       case PLPGSQL_DTYPE_ROW:
-                       case PLPGSQL_DTYPE_REC:
-                       case PLPGSQL_DTYPE_RECFIELD:
-                       case PLPGSQL_DTYPE_ARRAYELEM:
-                               break;
-
-                       default:
-                               elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
-               }
-       }
-
-       /*
         * Set the magic variable FOUND to false
         */
        exec_set_found(&estate, false);
@@ -445,7 +413,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
         * Make local execution copies of all the datums
         */
        estate.err_text = gettext_noop("during initialization of execution state");
-       for (i = 0; i < func->ndatums; i++)
+       for (i = 0; i < estate.ndatums; i++)
                estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
 
        /*
@@ -551,7 +519,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        var->freeval = false;
 
        /*
-        * Store the actual call argument values into the special execution
+        * Store the trigger argument values into the special execution
         * state variables
         */
        estate.err_text = gettext_noop("while storing call arguments into local variables");
@@ -567,37 +535,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        }
 
        /*
-        * Initialize the other variables to NULL values for now. The default
-        * values are set when the blocks are entered.
-        */
-       estate.err_text = gettext_noop("while initializing local variables to NULL");
-       for (i = estate.found_varno; i < estate.ndatums; i++)
-       {
-               switch (estate.datums[i]->dtype)
-               {
-                       case PLPGSQL_DTYPE_VAR:
-                               {
-                                       PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i];
-
-                                       var->value = 0;
-                                       var->isnull = true;
-                                       var->freeval = false;
-                               }
-                               break;
-
-                       case PLPGSQL_DTYPE_ROW:
-                       case PLPGSQL_DTYPE_REC:
-                       case PLPGSQL_DTYPE_RECFIELD:
-                       case PLPGSQL_DTYPE_ARRAYELEM:
-                       case PLPGSQL_DTYPE_TRIGARG:
-                               break;
-
-                       default:
-                               elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
-               }
-       }
-
-       /*
         * Set the magic variable FOUND to false
         */
        exec_set_found(&estate, false);
@@ -710,67 +647,65 @@ plpgsql_exec_error_callback(void *arg)
 
 
 /* ----------
- * Support functions for copying local execution variables
- *
- * NB: this is not a generic copy operation because it assumes that any
- * pass-by-ref original values will live as long as the copy is needed.
+ * Support function for initializing local execution variables
  * ----------
  */
 static PLpgSQL_datum *
 copy_plpgsql_datum(PLpgSQL_datum *datum)
 {
-       PLpgSQL_datum *result = NULL;
+       PLpgSQL_datum *result;
 
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
-                       result = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) datum);
-                       break;
+               {
+                       PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
+
+                       memcpy(new, datum, sizeof(PLpgSQL_var));
+                       /* Ensure the value is null (possibly not needed?) */
+                       new->value = 0;
+                       new->isnull = true;
+                       new->freeval = false;
+
+                       result = (PLpgSQL_datum *) new;
+               }
+               break;
 
                case PLPGSQL_DTYPE_REC:
-                       result = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) datum);
-                       break;
+               {
+                       PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
+
+                       memcpy(new, datum, sizeof(PLpgSQL_rec));
+                       /* Ensure the value is null (possibly not needed?) */
+                       new->tup = NULL;
+                       new->tupdesc = NULL;
+                       new->freetup = false;
+                       new->freetupdesc = false;
+
+                       result = (PLpgSQL_datum *) new;
+               }
+               break;
 
                case PLPGSQL_DTYPE_ROW:
                case PLPGSQL_DTYPE_RECFIELD:
                case PLPGSQL_DTYPE_ARRAYELEM:
                case PLPGSQL_DTYPE_TRIGARG:
+                       /*
+                        * These datum records are read-only at runtime, so no need
+                        * to copy them
+                        */
                        result = datum;
                        break;
 
                default:
                        elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+                       result = NULL;          /* keep compiler quiet */
+                       break;
        }
 
        return result;
 }
 
-static PLpgSQL_var *
-copy_var(PLpgSQL_var *var)
-{
-       PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
-
-       memcpy(new, var, sizeof(PLpgSQL_var));
-       new->freeval = false;
-
-       return new;
-}
-
-
-static PLpgSQL_rec *
-copy_rec(PLpgSQL_rec *rec)
-{
-       PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
-
-       memcpy(new, rec, sizeof(PLpgSQL_rec));
-       new->tup = NULL;
-       new->tupdesc = NULL;
-       new->freetup = false;
-       new->freetupdesc = false;
-
-       return new;
-}
-
 
 static bool
 exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
@@ -1682,43 +1617,64 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
        if (estate->retisset)
                return PLPGSQL_RC_RETURN;
 
-       if (estate->retistuple)
+       /* initialize for null result (possibly a tuple) */
+       estate->retval = (Datum) 0;
+       estate->rettupdesc = NULL;
+       estate->retisnull = true;
+
+       if (stmt->retvarno >= 0)
        {
-               /* initialize for null result tuple */
-               estate->retval = (Datum) 0;
-               estate->rettupdesc = NULL;
-               estate->retisnull = true;
+               PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
 
-               if (stmt->retrecno >= 0)
+               switch (retvar->dtype)
                {
-                       PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]);
-
-                       if (HeapTupleIsValid(rec->tup))
+                       case PLPGSQL_DTYPE_VAR:
                        {
-                               estate->retval = (Datum) rec->tup;
-                               estate->rettupdesc = rec->tupdesc;
-                               estate->retisnull = false;
+                               PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+
+                               estate->retval = var->value;
+                               estate->retisnull = var->isnull;
+                               estate->rettype = var->datatype->typoid;
                        }
-                       return PLPGSQL_RC_RETURN;
-               }
+                       break;
 
-               if (stmt->retrowno >= 0)
-               {
-                       PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]);
+                       case PLPGSQL_DTYPE_REC:
+                       {
+                               PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
 
-                       if (row->rowtupdesc)    /* should always be true here */
+                               if (HeapTupleIsValid(rec->tup))
+                               {
+                                       estate->retval = (Datum) rec->tup;
+                                       estate->rettupdesc = rec->tupdesc;
+                                       estate->retisnull = false;
+                               }
+                       }
+                       break;
+
+                       case PLPGSQL_DTYPE_ROW:
                        {
+                               PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+
+                               Assert(row->rowtupdesc);
                                estate->retval = (Datum) make_tuple_from_row(estate, row,
-                                                                                                               row->rowtupdesc);
-                               if (estate->retval == (Datum) NULL)             /* should not happen */
+                                                                                                                        row->rowtupdesc);
+                               if (estate->retval == (Datum) NULL)     /* should not happen */
                                        elog(ERROR, "row not compatible with its own tupdesc");
                                estate->rettupdesc = row->rowtupdesc;
                                estate->retisnull = false;
                        }
-                       return PLPGSQL_RC_RETURN;
+                       break;
+
+                       default:
+                               elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
                }
 
-               if (stmt->expr != NULL)
+               return PLPGSQL_RC_RETURN;
+       }
+
+       if (stmt->expr != NULL)
+       {
+               if (estate->retistuple)
                {
                        exec_run_select(estate, stmt->expr, 1, NULL);
                        if (estate->eval_processed > 0)
@@ -1728,24 +1684,23 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
                                estate->retisnull = false;
                        }
                }
-               return PLPGSQL_RC_RETURN;
+               else
+               {
+                       /* Normal case for scalar results */
+                       estate->retval = exec_eval_expr(estate, stmt->expr,
+                                                                                       &(estate->retisnull),
+                                                                                       &(estate->rettype));
+               }
        }
 
        if (estate->fn_rettype == VOIDOID)
        {
                /* Special hack for function returning VOID */
-               Assert(stmt->expr == NULL);
+               Assert(stmt->retvarno < 0 && stmt->expr == NULL);
                estate->retval = (Datum) 0;
                estate->retisnull = false;
                estate->rettype = VOIDOID;
        }
-       else
-       {
-               /* Normal case for scalar results */
-               estate->retval = exec_eval_expr(estate, stmt->expr,
-                                                                               &(estate->retisnull),
-                                                                               &(estate->rettype));
-       }
 
        return PLPGSQL_RC_RETURN;
 }
@@ -1777,37 +1732,78 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
        tupdesc = estate->rettupdesc;
        natts = tupdesc->natts;
 
-       if (stmt->rec)
+       if (stmt->retvarno >= 0)
        {
-               PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+               PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
 
-               if (!HeapTupleIsValid(rec->tup))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                        errmsg("record \"%s\" is not assigned yet",
-                                                       rec->refname),
-                                        errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
-               if (!compatible_tupdesc(tupdesc, rec->tupdesc))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                  errmsg("wrong record type supplied in RETURN NEXT")));
-               tuple = rec->tup;
-       }
-       else if (stmt->row)
-       {
-               tuple = make_tuple_from_row(estate, stmt->row, tupdesc);
-               if (tuple == NULL)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                  errmsg("wrong record type supplied in RETURN NEXT")));
-               free_tuple = true;
+               switch (retvar->dtype)
+               {
+                       case PLPGSQL_DTYPE_VAR:
+                       {
+                               PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+                               Datum           retval = var->value;
+                               bool            isNull = var->isnull;
+
+                               if (natts != 1)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("wrong result type supplied in RETURN NEXT")));
+
+                               /* coerce type if needed */
+                               retval = exec_simple_cast_value(retval,
+                                                                                               var->datatype->typoid,
+                                                                                               tupdesc->attrs[0]->atttypid,
+                                                                                               tupdesc->attrs[0]->atttypmod,
+                                                                                               &isNull);
+
+                               tuple = heap_form_tuple(tupdesc, &retval, &isNull);
+
+                               free_tuple = true;
+                       }
+                       break;
+
+                       case PLPGSQL_DTYPE_REC:
+                       {
+                               PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+
+                               if (!HeapTupleIsValid(rec->tup))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                                        errmsg("record \"%s\" is not assigned yet",
+                                                                       rec->refname),
+                                                        errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+                               if (!compatible_tupdesc(tupdesc, rec->tupdesc))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("wrong record type supplied in RETURN NEXT")));
+                               tuple = rec->tup;
+                       }
+                       break;
+
+                       case PLPGSQL_DTYPE_ROW:
+                       {
+                               PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+
+                               tuple = make_tuple_from_row(estate, row, tupdesc);
+                               if (tuple == NULL)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("wrong record type supplied in RETURN NEXT")));
+                               free_tuple = true;
+                       }
+                       break;
+
+                       default:
+                               elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
+                               tuple = NULL;   /* keep compiler quiet */
+                               break;
+               }
        }
        else if (stmt->expr)
        {
                Datum           retval;
                bool            isNull;
                Oid                     rettype;
-               char            nullflag;
 
                if (natts != 1)
                        ereport(ERROR,
@@ -1826,9 +1822,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                                                tupdesc->attrs[0]->atttypmod,
                                                                                &isNull);
 
-               nullflag = isNull ? 'n' : ' ';
-
-               tuple = heap_formtuple(tupdesc, &retval, &nullflag);
+               tuple = heap_form_tuple(tupdesc, &retval, &isNull);
 
                free_tuple = true;
 
index 91d7e15..475d222 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.39 2005/02/22 07:18:24 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.40 2005/04/05 06:22:16 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -857,14 +857,12 @@ dump_return(PLpgSQL_stmt_return *stmt)
 {
        dump_ind();
        printf("RETURN ");
-       if (stmt->retrecno >= 0)
-               printf("record %d", stmt->retrecno);
-       else if (stmt->retrowno >= 0)
-               printf("row %d", stmt->retrowno);
-       else if (stmt->expr == NULL)
-               printf("NULL");
-       else
+       if (stmt->retvarno >= 0)
+               printf("variable %d", stmt->retvarno);
+       else if (stmt->expr != NULL)
                dump_expr(stmt->expr);
+       else
+               printf("NULL");
        printf("\n");
 }
 
@@ -873,12 +871,12 @@ dump_return_next(PLpgSQL_stmt_return_next *stmt)
 {
        dump_ind();
        printf("RETURN NEXT ");
-       if (stmt->rec != NULL)
-               printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
-       else if (stmt->row != NULL)
-               printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname);
+       if (stmt->retvarno >= 0)
+               printf("variable %d", stmt->retvarno);
        else if (stmt->expr != NULL)
                dump_expr(stmt->expr);
+       else
+               printf("NULL");
        printf("\n");
 }
 
index df38351..80bcd9e 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.57 2005/02/22 07:18:24 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.58 2005/04/05 06:22:16 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -491,17 +491,15 @@ typedef struct
        int                     cmd_type;
        int                     lineno;
        PLpgSQL_expr *expr;
-       int                     retrecno;
-       int                     retrowno;
+       int                     retvarno;
 } PLpgSQL_stmt_return;
 
 typedef struct
 {                                                              /* RETURN NEXT statement */
        int                     cmd_type;
        int                     lineno;
-       PLpgSQL_rec *rec;
-       PLpgSQL_row *row;
        PLpgSQL_expr *expr;
+       int                     retvarno;
 } PLpgSQL_stmt_return_next;
 
 typedef struct
@@ -572,6 +570,7 @@ typedef struct PLpgSQL_function
 
        int                     fn_nargs;
        int                     fn_argvarnos[FUNC_MAX_ARGS];
+       int                     out_param_varno;
        int                     found_varno;
        int                     new_varno;
        int                     old_varno;
index 7fec95a..ee1c52d 100644 (file)
@@ -1739,6 +1739,125 @@ SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
 (1 row)
 
 --
+-- Test handling of OUT parameters, including polymorphic cases
+--
+-- wrong way to do it:
+create function f1(in i int, out j int) returns int as $$
+begin
+  return i+1;
+end$$ language plpgsql;
+ERROR:  RETURN cannot have a parameter in function with OUT parameters at or near "i" at character 74
+LINE 3:   return i+1;
+                 ^
+create function f1(in i int, out j int) as $$
+begin
+  j := i+1;
+  return;
+end$$ language plpgsql;
+select f1(42);
+ f1 
+----
+ 43
+(1 row)
+
+select * from f1(42);
+ f1 
+----
+ 43
+(1 row)
+
+create or replace function f1(inout i int) as $$
+begin
+  i := i+1;
+  return;
+end$$ language plpgsql;
+select f1(42);
+ f1 
+----
+ 43
+(1 row)
+
+select * from f1(42);
+ f1 
+----
+ 43
+(1 row)
+
+drop function f1(int);
+create function f1(in i int, out j int) returns setof int as $$
+begin
+  j := i+1;
+  return next;
+  j := i+2;
+  return next;
+  return;
+end$$ language plpgsql;
+select * from f1(42);
+ f1 
+----
+ 43
+ 44
+(2 rows)
+
+drop function f1(int);
+create function f1(in i int, out j int, out k text) as $$
+begin
+  j := i;
+  j := j+1;
+  k := 'foo';
+  return;
+end$$ language plpgsql;
+select f1(42);
+    f1    
+----------
+ (43,foo)
+(1 row)
+
+select * from f1(42);
+ j  |  k  
+----+-----
+ 43 | foo
+(1 row)
+
+drop function f1(int);
+create function f1(in i int, out j int, out k text) returns setof record as $$
+begin
+  j := i+1;
+  k := 'foo';
+  return next;
+  j := j+1;
+  k := 'foot';
+  return next;
+  return;
+end$$ language plpgsql;
+select * from f1(42);
+ j  |  k   
+----+------
+ 43 | foo
+ 44 | foot
+(2 rows)
+
+drop function f1(int);
+create function dup(in i anyelement, out j anyelement, out k anyarray) as $$
+begin
+  j := i;
+  k := array[j,j];
+  return;
+end$$ language plpgsql;
+select * from dup(42);
+ j  |    k    
+----+---------
+ 42 | {42,42}
+(1 row)
+
+select * from dup('foo'::text);
+  j  |     k     
+-----+-----------
+ foo | {foo,foo}
+(1 row)
+
+drop function dup(anyelement);
+--
 -- test PERFORM
 --
 create table perform_test (
index 607b7f2..e807961 100644 (file)
@@ -1561,6 +1561,89 @@ SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int);
 SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
 
 --
+-- Test handling of OUT parameters, including polymorphic cases
+--
+
+-- wrong way to do it:
+create function f1(in i int, out j int) returns int as $$
+begin
+  return i+1;
+end$$ language plpgsql;
+
+create function f1(in i int, out j int) as $$
+begin
+  j := i+1;
+  return;
+end$$ language plpgsql;
+
+select f1(42);
+select * from f1(42);
+
+create or replace function f1(inout i int) as $$
+begin
+  i := i+1;
+  return;
+end$$ language plpgsql;
+
+select f1(42);
+select * from f1(42);
+
+drop function f1(int);
+
+create function f1(in i int, out j int) returns setof int as $$
+begin
+  j := i+1;
+  return next;
+  j := i+2;
+  return next;
+  return;
+end$$ language plpgsql;
+
+select * from f1(42);
+
+drop function f1(int);
+
+create function f1(in i int, out j int, out k text) as $$
+begin
+  j := i;
+  j := j+1;
+  k := 'foo';
+  return;
+end$$ language plpgsql;
+
+select f1(42);
+select * from f1(42);
+
+drop function f1(int);
+
+create function f1(in i int, out j int, out k text) returns setof record as $$
+begin
+  j := i+1;
+  k := 'foo';
+  return next;
+  j := j+1;
+  k := 'foot';
+  return next;
+  return;
+end$$ language plpgsql;
+
+select * from f1(42);
+
+drop function f1(int);
+
+create function dup(in i anyelement, out j anyelement, out k anyarray) as $$
+begin
+  j := i;
+  k := array[j,j];
+  return;
+end$$ language plpgsql;
+
+select * from dup(42);
+select * from dup('foo'::text);
+
+drop function dup(anyelement);
+
+--
 -- test PERFORM
 --
 
@@ -1917,4 +2000,4 @@ end;$$ language plpgsql;
 create function void_return_expr() returns void as $$
 begin
     return 5;
-end;$$ language plpgsql;
\ No newline at end of file
+end;$$ language plpgsql;