Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Support for OUT parameters in procedures
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 5 Oct 2020 07:09:09 +0000 (09:09 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 5 Oct 2020 07:21:43 +0000 (09:21 +0200)
Unlike for functions, OUT parameters for procedures are part of the
signature.  Therefore, they have to be listed in pg_proc.proargtypes
as well as mentioned in ALTER PROCEDURE and DROP PROCEDURE.

Reviewed-by: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
Reviewed-by: Pavel Stehule <pavel.stehule@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/2b8490fe-51af-e671-c504-47359dc453c5@2ndquadrant.com

28 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/plpgsql.sgml
doc/src/sgml/ref/alter_extension.sgml
doc/src/sgml/ref/alter_procedure.sgml
doc/src/sgml/ref/comment.sgml
doc/src/sgml/ref/create_procedure.sgml
doc/src/sgml/ref/drop_procedure.sgml
doc/src/sgml/ref/security_label.sgml
doc/src/sgml/xfunc.sgml
src/backend/catalog/pg_proc.c
src/backend/commands/functioncmds.c
src/backend/executor/functions.c
src/backend/parser/gram.y
src/backend/utils/fmgr/funcapi.c
src/include/catalog/pg_proc.h
src/include/funcapi.h
src/pl/plperl/expected/plperl_call.out
src/pl/plperl/sql/plperl_call.sql
src/pl/plpgsql/src/expected/plpgsql_call.out
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/sql/plpgsql_call.sql
src/pl/plpython/expected/plpython_call.out
src/pl/plpython/plpy_procedure.c
src/pl/plpython/sql/plpython_call.sql
src/pl/tcl/expected/pltcl_call.out
src/pl/tcl/sql/pltcl_call.sql
src/test/regress/expected/create_procedure.out
src/test/regress/sql/create_procedure.sql

index 0e580b157f5355d67f0ca045804b9cfa4c6fb4f9..3927b1030df4f6cf7b4f4115d8364d8b4d09be9b 100644 (file)
@@ -5875,8 +5875,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <para>
        An array with the data types of the function arguments.  This includes
        only input arguments (including <literal>INOUT</literal> and
-       <literal>VARIADIC</literal> arguments), and thus represents
-       the call signature of the function.
+       <literal>VARIADIC</literal> arguments), as well as
+       <literal>OUT</literal> parameters of procedures, and thus represents
+       the call signature of the function or procedure.
       </para></entry>
      </row>
 
index c2bb3e326851a49ed3dee48f8899196a2934bbb6..74b6b2587809eef9e0f0286366b9d84ce528483d 100644 (file)
@@ -478,6 +478,14 @@ $$ LANGUAGE plpgsql;
       included it, but it would be redundant.
      </para>
 
+     <para>
+      To call a function with <literal>OUT</literal> parameters, omit the
+      output parameter in the function call:
+<programlisting>
+SELECT sales_tax(100.00);
+</programlisting>
+     </para>
+
      <para>
       Output parameters are most useful when returning multiple values.
       A trivial example is:
@@ -489,6 +497,11 @@ BEGIN
     prod := x * y;
 END;
 $$ LANGUAGE plpgsql;
+
+SELECT * FROM sum_n_product(2, 4);
+ sum | prod
+-----+------
+   6 |    8
 </programlisting>
 
       As discussed in <xref linkend="xfunc-output-parameters"/>, this
@@ -497,6 +510,31 @@ $$ LANGUAGE plpgsql;
       <literal>RETURNS record</literal>.
      </para>
 
+     <para>
+      This also works with procedures, for example:
+
+<programlisting>
+CREATE PROCEDURE sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
+BEGIN
+    sum := x + y;
+    prod := x * y;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+
+      In a call to a procedure, all the parameters must be specified.  For
+      output parameters, <literal>NULL</literal> may be specified.
+<programlisting>
+CALL sum_n_product(2, 4, NULL, NULL);
+ sum | prod
+-----+------
+   6 |    8
+</programlisting>
+      Output parameters in procedures become more interesting in nested calls,
+      where they can be assigned to variables.  See <xref
+      linkend="plpgsql-statements-calling-procedure"/> for details.
+     </para>
+
      <para>
       Another way to declare a <application>PL/pgSQL</application> function
       is with <literal>RETURNS TABLE</literal>, for example:
index c819c7bb4e3c474d4efcb25a9538a1cb5c978175..38fd60128b7830f283477961861ee05bccb6c15a 100644 (file)
@@ -212,11 +212,12 @@ ALTER EXTENSION <replaceable class="parameter">name</replaceable> DROP <replacea
        argument: <literal>IN</literal>, <literal>OUT</literal>,
        <literal>INOUT</literal>, or <literal>VARIADIC</literal>.
        If omitted, the default is <literal>IN</literal>.
-       Note that <command>ALTER EXTENSION</command> does not actually pay
-       any attention to <literal>OUT</literal> arguments, since only the input
-       arguments are needed to determine the function's identity.
-       So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>,
-       and <literal>VARIADIC</literal> arguments.
+       Note that <command>ALTER EXTENSION</command> does not actually pay any
+       attention to <literal>OUT</literal> arguments for functions and
+       aggregates (but not procedures), since only the input arguments are
+       needed to determine the function's identity.  So it is sufficient to
+       list the <literal>IN</literal>, <literal>INOUT</literal>, and
+       <literal>VARIADIC</literal> arguments for functions and aggregates.
       </para>
      </listitem>
     </varlistentry>
index bcf45c7a85f95cd5827444d9a4e3c7d0295ad95e..5c176fb5d876077c7232bbc6090372899b03d49f 100644 (file)
@@ -81,8 +81,9 @@ ALTER PROCEDURE <replaceable>name</replaceable> [ ( [ [ <replaceable class="para
 
     <listitem>
      <para>
-      The mode of an argument: <literal>IN</literal>  or <literal>VARIADIC</literal>.
-      If omitted, the default is <literal>IN</literal>.
+      The mode of an argument: <literal>IN</literal>, <literal>OUT</literal>,
+      <literal>INOUT</literal>, or <literal>VARIADIC</literal>.  If omitted,
+      the default is <literal>IN</literal>.
      </para>
     </listitem>
    </varlistentry>
index 6e8ced3eaf1191d49901a58f897a625595ab5a85..eda91b4e240f59f7ec35742cabb366ee7a8e2b31 100644 (file)
@@ -178,11 +178,12 @@ COMMENT ON
       argument: <literal>IN</literal>, <literal>OUT</literal>,
       <literal>INOUT</literal>, or <literal>VARIADIC</literal>.
       If omitted, the default is <literal>IN</literal>.
-      Note that <command>COMMENT</command> does not actually pay
-      any attention to <literal>OUT</literal> arguments, since only the input
-      arguments are needed to determine the function's identity.
-      So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>,
-      and <literal>VARIADIC</literal> arguments.
+      Note that <command>COMMENT</command> does not actually pay any attention
+      to <literal>OUT</literal> arguments for functions and aggregates (but
+      not procedures), since only the input arguments are needed to determine
+      the function's identity.  So it is sufficient to list the
+      <literal>IN</literal>, <literal>INOUT</literal>, and
+      <literal>VARIADIC</literal> arguments for functions and aggregates.
      </para>
     </listitem>
    </varlistentry>
index 36c307cadc796c40d0733a2e4edb5876dc326358..e258eca5ceeae267efecd0ab6b68a251f84479b5 100644 (file)
@@ -97,11 +97,9 @@ CREATE [ OR REPLACE ] PROCEDURE
 
      <listitem>
       <para>
-       The mode of an argument: <literal>IN</literal>,
+       The mode of an argument: <literal>IN</literal>, <literal>OUT</literal>,
        <literal>INOUT</literal>, or <literal>VARIADIC</literal>.  If omitted,
-       the default is <literal>IN</literal>.  (<literal>OUT</literal>
-       arguments are currently not supported for procedures.  Use
-       <literal>INOUT</literal> instead.)
+       the default is <literal>IN</literal>.
       </para>
      </listitem>
     </varlistentry>
index 6da266ae2dae970f3f226d0de036a618c38c2b16..bf2c6ce1aaa1a27b40f06c015d1b7a63a8cd9399 100644 (file)
@@ -67,8 +67,9 @@ DROP PROCEDURE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [
 
     <listitem>
      <para>
-      The mode of an argument: <literal>IN</literal> or <literal>VARIADIC</literal>.
-      If omitted, the default is <literal>IN</literal>.
+      The mode of an argument: <literal>IN</literal>, <literal>OUT</literal>,
+      <literal>INOUT</literal>, or <literal>VARIADIC</literal>.  If omitted,
+      the default is <literal>IN</literal>.
      </para>
     </listitem>
    </varlistentry>
index e9688cce214b22c56ccac4a0898876ab4dab7adb..9b87bcd51961cb703199a7ff34674f1dc45aee34 100644 (file)
@@ -127,11 +127,12 @@ SECURITY LABEL [ FOR <replaceable class="parameter">provider</replaceable> ] ON
       argument: <literal>IN</literal>, <literal>OUT</literal>,
       <literal>INOUT</literal>, or <literal>VARIADIC</literal>.
       If omitted, the default is <literal>IN</literal>.
-      Note that <command>SECURITY LABEL</command> does not actually
-      pay any attention to <literal>OUT</literal> arguments, since only the input
-      arguments are needed to determine the function's identity.
-      So it is sufficient to list the <literal>IN</literal>, <literal>INOUT</literal>,
-      and <literal>VARIADIC</literal> arguments.
+      Note that <command>SECURITY LABEL</command> does not actually pay any
+      attention to <literal>OUT</literal> arguments for functions and
+      aggregates (but not procedures), since only the input arguments are
+      needed to determine the function's identity.  So it is sufficient to
+      list the <literal>IN</literal>, <literal>INOUT</literal>, and
+      <literal>VARIADIC</literal> arguments for functions and aggregates.
      </para>
     </listitem>
    </varlistentry>
index 8c74c11d3b599efa4c49ec195b9efa254188ccab..2863f7c20657fb757b84c8b9fca736f68ab35bde 100644 (file)
@@ -179,6 +179,24 @@ SELECT clean_emp();
 </screen>
     </para>
 
+    <para>
+     You can also write this as a procedure, thus avoiding the issue of the
+     return type.  For example:
+<screen>
+CREATE PROCEDURE clean_emp() AS '
+    DELETE FROM emp
+        WHERE salary &lt; 0;
+' LANGUAGE SQL;
+
+CALL clean_emp();
+</screen>
+     In simple cases like this, the difference between a function returning
+     <type>void</type> and a procedure is mostly stylistic.  However,
+     procedures offer additional functionality such as transaction control
+     that is not available in functions.  Also, procedures are SQL standard
+     whereas returning <type>void</type> is a PostgreSQL extension.
+    </para>
+
     <note>
      <para>
       The entire body of a SQL function is parsed before any of it is
@@ -716,6 +734,47 @@ DROP FUNCTION sum_n_product (int, int);
     </para>
    </sect2>
 
+   <sect2 id="xfunc-output-parameters-proc">
+    <title><acronym>SQL</acronym> Procedures with Output Parameters</title>
+
+    <indexterm>
+     <primary>procedures</primary>
+     <secondary>output parameter</secondary>
+    </indexterm>
+
+    <para>
+     Output parameters are also supported in procedures, but they work a bit
+     differently from functions.  Notably, output parameters
+     <emphasis>are</emphasis> included in the signature of a procedure and
+     must be specified in the procedure call.
+    </para>
+
+    <para>
+     For example, the bank account debiting routine from earlier could be
+     written like this:
+<programlisting>
+CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$
+    UPDATE bank
+        SET balance = balance - debit
+        WHERE accountno = tp1.accountno
+    RETURNING balance;
+$$ LANGUAGE SQL;
+</programlisting>
+     To call this procedure, it is irrelevant what is passed as the argument
+     of the <literal>OUT</literal> parameter, so you could pass
+     <literal>NULL</literal>:
+<programlisting>
+CALL tp1(17, 100.0, NULL);
+</programlisting>
+    </para>
+
+    <para>
+     Procedures with output parameters are more useful in PL/pgSQL, where the
+     output parameters can be assigned to variables.  See <xref
+     linkend="plpgsql-statements-calling-procedure"/> for details.
+    </para>
+   </sect2>
+
    <sect2 id="xfunc-sql-variadic-functions">
     <title><acronym>SQL</acronym> Functions with Variable Numbers of Arguments</title>
 
index 40d65dc6bab1f09c77f727b075a55852deb98f5e..f7dab9925b9e2138f49d4c80c039f9763e010481 100644 (file)
@@ -249,6 +249,9 @@ ProcedureCreate(const char *procedureName,
                        elog(ERROR, "variadic parameter must be last");
                    break;
                case PROARGMODE_OUT:
+                   if (OidIsValid(variadicType) && prokind == PROKIND_PROCEDURE)
+                       elog(ERROR, "variadic parameter must be last");
+                   break;
                case PROARGMODE_TABLE:
                    /* okay */
                    break;
@@ -462,10 +465,12 @@ ProcedureCreate(const char *procedureName,
            if (isnull)
                proargmodes = PointerGetDatum(NULL);    /* just to be sure */
 
-           n_old_arg_names = get_func_input_arg_names(proargnames,
+           n_old_arg_names = get_func_input_arg_names(prokind,
+                                                      proargnames,
                                                       proargmodes,
                                                       &old_arg_names);
-           n_new_arg_names = get_func_input_arg_names(parameterNames,
+           n_new_arg_names = get_func_input_arg_names(prokind,
+                                                      parameterNames,
                                                       parameterModes,
                                                       &new_arg_names);
            for (j = 0; j < n_old_arg_names; j++)
index e236581a8e06c7f066403c9a9a922349aa54175c..c3ce480c8f568a9f885af1c84a762273816148bd 100644 (file)
@@ -194,8 +194,8 @@ interpret_function_parameter_list(ParseState *pstate,
                                  Oid *requiredResultType)
 {
    int         parameterCount = list_length(parameters);
-   Oid        *inTypes;
-   int         inCount = 0;
+   Oid        *sigArgTypes;
+   int         sigArgCount = 0;
    Datum      *allTypes;
    Datum      *paramModes;
    Datum      *paramNames;
@@ -209,7 +209,7 @@ interpret_function_parameter_list(ParseState *pstate,
    *variadicArgType = InvalidOid;  /* default result */
    *requiredResultType = InvalidOid;   /* default result */
 
-   inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
+   sigArgTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
    allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
    paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
    paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
@@ -281,25 +281,21 @@ interpret_function_parameter_list(ParseState *pstate,
                         errmsg("functions cannot accept set arguments")));
        }
 
-       if (objtype == OBJECT_PROCEDURE)
-       {
-           if (fp->mode == FUNC_PARAM_OUT)
-               ereport(ERROR,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("procedures cannot have OUT arguments"),
-                        errhint("INOUT arguments are permitted.")));
-       }
-
        /* handle input parameters */
        if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+           isinput = true;
+
+       /* handle signature parameters */
+       if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
+           (objtype == OBJECT_PROCEDURE && fp->mode == FUNC_PARAM_OUT) ||
+           fp->mode == FUNC_PARAM_VARIADIC)
        {
-           /* other input parameters can't follow a VARIADIC parameter */
+           /* other signature parameters can't follow a VARIADIC parameter */
            if (varCount > 0)
                ereport(ERROR,
                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-                        errmsg("VARIADIC parameter must be the last input parameter")));
-           inTypes[inCount++] = toid;
-           isinput = true;
+                        errmsg("VARIADIC parameter must be the last signature parameter")));
+           sigArgTypes[sigArgCount++] = toid;
        }
 
        /* handle output parameters */
@@ -429,7 +425,7 @@ interpret_function_parameter_list(ParseState *pstate,
    }
 
    /* Now construct the proper outputs as needed */
-   *parameterTypes = buildoidvector(inTypes, inCount);
+   *parameterTypes = buildoidvector(sigArgTypes, sigArgCount);
 
    if (outCount > 0 || varCount > 0)
    {
@@ -2067,6 +2063,9 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
    int         nargs;
    int         i;
    AclResult   aclresult;
+   Oid        *argtypes;
+   char      **argnames;
+   char       *argmodes;
    FmgrInfo    flinfo;
    CallContext *callcontext;
    EState     *estate;
@@ -2127,6 +2126,8 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
                                            tp);
    nargs = list_length(fexpr->args);
 
+   get_func_arg_info(tp, &argtypes, &argnames, &argmodes);
+
    ReleaseSysCache(tp);
 
    /* safety check; see ExecInitFunc() */
@@ -2156,16 +2157,24 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
    i = 0;
    foreach(lc, fexpr->args)
    {
-       ExprState  *exprstate;
-       Datum       val;
-       bool        isnull;
+       if (argmodes && argmodes[i] == PROARGMODE_OUT)
+       {
+           fcinfo->args[i].value = 0;
+           fcinfo->args[i].isnull = true;
+       }
+       else
+       {
+           ExprState  *exprstate;
+           Datum       val;
+           bool        isnull;
 
-       exprstate = ExecPrepareExpr(lfirst(lc), estate);
+           exprstate = ExecPrepareExpr(lfirst(lc), estate);
 
-       val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
+           val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
 
-       fcinfo->args[i].value = val;
-       fcinfo->args[i].isnull = isnull;
+           fcinfo->args[i].value = val;
+           fcinfo->args[i].isnull = isnull;
+       }
 
        i++;
    }
index f940f48c6dae1605671869d0336824cd790f0bf4..bf00a9c1e8da2ba2c201152534b05846ac85be48 100644 (file)
@@ -259,7 +259,8 @@ prepare_sql_fn_parse_info(HeapTuple procedureTuple,
        if (isNull)
            proargmodes = PointerGetDatum(NULL);    /* just to be sure */
 
-       n_arg_names = get_func_input_arg_names(proargnames, proargmodes,
+       n_arg_names = get_func_input_arg_names(procedureStruct->prokind,
+                                              proargnames, proargmodes,
                                               &pinfo->argnames);
 
        /* Paranoia: ignore the result if too few array entries */
index 17653ef3a79090b73ea2555953a0a051ef619c9f..0d101d8171545c5abb1f57bfb837e246d1b7e954 100644 (file)
@@ -166,7 +166,7 @@ static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
 static void check_qualified_name(List *names, core_yyscan_t yyscanner);
 static List *check_func_name(List *names, core_yyscan_t yyscanner);
 static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
-static List *extractArgTypes(List *parameters);
+static List *extractArgTypes(ObjectType objtype, List *parameters);
 static List *extractAggrArgTypes(List *aggrargs);
 static List *makeOrderedSetArgs(List *directargs, List *orderedargs,
                                core_yyscan_t yyscanner);
@@ -375,8 +375,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <accesspriv> privilege
 %type <list>   privileges privilege_list
 %type <privtarget> privilege_target
-%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
-%type <list>   function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
+%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes procedure_with_argtypes function_with_argtypes_common
+%type <list>   function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list procedure_with_argtypes_list
 %type <ival>   defacl_privilege_target
 %type <defelt> DefACLOption
 %type <list>   DefACLOptionList
@@ -4623,7 +4623,7 @@ AlterExtensionContentsStmt:
                    n->object = (Node *) lcons(makeString($9), $7);
                    $$ = (Node *)n;
                }
-           | ALTER EXTENSION name add_drop PROCEDURE function_with_argtypes
+           | ALTER EXTENSION name add_drop PROCEDURE procedure_with_argtypes
                {
                    AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
                    n->extname = $3;
@@ -4632,7 +4632,7 @@ AlterExtensionContentsStmt:
                    n->object = (Node *) $6;
                    $$ = (Node *)n;
                }
-           | ALTER EXTENSION name add_drop ROUTINE function_with_argtypes
+           | ALTER EXTENSION name add_drop ROUTINE procedure_with_argtypes
                {
                    AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
                    n->extname = $3;
@@ -6365,7 +6365,7 @@ CommentStmt:
                    n->comment = $8;
                    $$ = (Node *) n;
                }
-           | COMMENT ON PROCEDURE function_with_argtypes IS comment_text
+           | COMMENT ON PROCEDURE procedure_with_argtypes IS comment_text
                {
                    CommentStmt *n = makeNode(CommentStmt);
                    n->objtype = OBJECT_PROCEDURE;
@@ -6373,7 +6373,7 @@ CommentStmt:
                    n->comment = $6;
                    $$ = (Node *) n;
                }
-           | COMMENT ON ROUTINE function_with_argtypes IS comment_text
+           | COMMENT ON ROUTINE procedure_with_argtypes IS comment_text
                {
                    CommentStmt *n = makeNode(CommentStmt);
                    n->objtype = OBJECT_ROUTINE;
@@ -6519,7 +6519,7 @@ SecLabelStmt:
                    n->label = $9;
                    $$ = (Node *) n;
                }
-           | SECURITY LABEL opt_provider ON PROCEDURE function_with_argtypes
+           | SECURITY LABEL opt_provider ON PROCEDURE procedure_with_argtypes
              IS security_label
                {
                    SecLabelStmt *n = makeNode(SecLabelStmt);
@@ -6880,7 +6880,7 @@ privilege_target:
                    n->objs = $2;
                    $$ = n;
                }
-           | PROCEDURE function_with_argtypes_list
+           | PROCEDURE procedure_with_argtypes_list
                {
                    PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
                    n->targtype = ACL_TARGET_OBJECT;
@@ -6888,7 +6888,7 @@ privilege_target:
                    n->objs = $2;
                    $$ = n;
                }
-           | ROUTINE function_with_argtypes_list
+           | ROUTINE procedure_with_argtypes_list
                {
                    PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
                    n->targtype = ACL_TARGET_OBJECT;
@@ -7409,20 +7409,33 @@ function_with_argtypes_list:
                                                    { $$ = lappend($1, $3); }
        ;
 
+procedure_with_argtypes_list:
+           procedure_with_argtypes                 { $$ = list_make1($1); }
+           | procedure_with_argtypes_list ',' procedure_with_argtypes
+                                                   { $$ = lappend($1, $3); }
+       ;
+
 function_with_argtypes:
            func_name func_args
                {
                    ObjectWithArgs *n = makeNode(ObjectWithArgs);
                    n->objname = $1;
-                   n->objargs = extractArgTypes($2);
+                   n->objargs = extractArgTypes(OBJECT_FUNCTION, $2);
                    $$ = n;
                }
+           | function_with_argtypes_common
+               {
+                   $$ = $1;
+               }
+       ;
+
+function_with_argtypes_common:
            /*
             * Because of reduce/reduce conflicts, we can't use func_name
             * below, but we can write it out the long way, which actually
             * allows more cases.
             */
-           type_func_name_keyword
+           type_func_name_keyword
                {
                    ObjectWithArgs *n = makeNode(ObjectWithArgs);
                    n->objname = list_make1(makeString(pstrdup($1)));
@@ -7446,6 +7459,24 @@ function_with_argtypes:
                }
        ;
 
+/*
+ * This is different from function_with_argtypes in the call to
+ * extractArgTypes().
+ */
+procedure_with_argtypes:
+           func_name func_args
+               {
+                   ObjectWithArgs *n = makeNode(ObjectWithArgs);
+                   n->objname = $1;
+                   n->objargs = extractArgTypes(OBJECT_PROCEDURE, $2);
+                   $$ = n;
+               }
+           | function_with_argtypes_common
+               {
+                   $$ = $1;
+               }
+       ;
+
 /*
  * func_args_with_defaults is separate because we only want to accept
  * defaults in CREATE FUNCTION, not in ALTER etc.
@@ -7824,7 +7855,7 @@ AlterFunctionStmt:
                    n->actions = $4;
                    $$ = (Node *) n;
                }
-           | ALTER PROCEDURE function_with_argtypes alterfunc_opt_list opt_restrict
+           | ALTER PROCEDURE procedure_with_argtypes alterfunc_opt_list opt_restrict
                {
                    AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
                    n->objtype = OBJECT_PROCEDURE;
@@ -7832,7 +7863,7 @@ AlterFunctionStmt:
                    n->actions = $4;
                    $$ = (Node *) n;
                }
-           | ALTER ROUTINE function_with_argtypes alterfunc_opt_list opt_restrict
+           | ALTER ROUTINE procedure_with_argtypes alterfunc_opt_list opt_restrict
                {
                    AlterFunctionStmt *n = makeNode(AlterFunctionStmt);
                    n->objtype = OBJECT_ROUTINE;
@@ -7888,7 +7919,7 @@ RemoveFuncStmt:
                    n->concurrent = false;
                    $$ = (Node *)n;
                }
-           | DROP PROCEDURE function_with_argtypes_list opt_drop_behavior
+           | DROP PROCEDURE procedure_with_argtypes_list opt_drop_behavior
                {
                    DropStmt *n = makeNode(DropStmt);
                    n->removeType = OBJECT_PROCEDURE;
@@ -7898,7 +7929,7 @@ RemoveFuncStmt:
                    n->concurrent = false;
                    $$ = (Node *)n;
                }
-           | DROP PROCEDURE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
+           | DROP PROCEDURE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior
                {
                    DropStmt *n = makeNode(DropStmt);
                    n->removeType = OBJECT_PROCEDURE;
@@ -7908,7 +7939,7 @@ RemoveFuncStmt:
                    n->concurrent = false;
                    $$ = (Node *)n;
                }
-           | DROP ROUTINE function_with_argtypes_list opt_drop_behavior
+           | DROP ROUTINE procedure_with_argtypes_list opt_drop_behavior
                {
                    DropStmt *n = makeNode(DropStmt);
                    n->removeType = OBJECT_ROUTINE;
@@ -7918,7 +7949,7 @@ RemoveFuncStmt:
                    n->concurrent = false;
                    $$ = (Node *)n;
                }
-           | DROP ROUTINE IF_P EXISTS function_with_argtypes_list opt_drop_behavior
+           | DROP ROUTINE IF_P EXISTS procedure_with_argtypes_list opt_drop_behavior
                {
                    DropStmt *n = makeNode(DropStmt);
                    n->removeType = OBJECT_ROUTINE;
@@ -8393,7 +8424,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
                    n->missing_ok = true;
                    $$ = (Node *)n;
                }
-           | ALTER PROCEDURE function_with_argtypes RENAME TO name
+           | ALTER PROCEDURE procedure_with_argtypes RENAME TO name
                {
                    RenameStmt *n = makeNode(RenameStmt);
                    n->renameType = OBJECT_PROCEDURE;
@@ -8411,7 +8442,7 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
                    n->missing_ok = false;
                    $$ = (Node *)n;
                }
-           | ALTER ROUTINE function_with_argtypes RENAME TO name
+           | ALTER ROUTINE procedure_with_argtypes RENAME TO name
                {
                    RenameStmt *n = makeNode(RenameStmt);
                    n->renameType = OBJECT_ROUTINE;
@@ -8822,7 +8853,7 @@ AlterObjectDependsStmt:
                    n->remove = $4;
                    $$ = (Node *)n;
                }
-           | ALTER PROCEDURE function_with_argtypes opt_no DEPENDS ON EXTENSION name
+           | ALTER PROCEDURE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name
                {
                    AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
                    n->objectType = OBJECT_PROCEDURE;
@@ -8831,7 +8862,7 @@ AlterObjectDependsStmt:
                    n->remove = $4;
                    $$ = (Node *)n;
                }
-           | ALTER ROUTINE function_with_argtypes opt_no DEPENDS ON EXTENSION name
+           | ALTER ROUTINE procedure_with_argtypes opt_no DEPENDS ON EXTENSION name
                {
                    AlterObjectDependsStmt *n = makeNode(AlterObjectDependsStmt);
                    n->objectType = OBJECT_ROUTINE;
@@ -8962,7 +8993,7 @@ AlterObjectSchemaStmt:
                    n->missing_ok = false;
                    $$ = (Node *)n;
                }
-           | ALTER PROCEDURE function_with_argtypes SET SCHEMA name
+           | ALTER PROCEDURE procedure_with_argtypes SET SCHEMA name
                {
                    AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
                    n->objectType = OBJECT_PROCEDURE;
@@ -8971,7 +9002,7 @@ AlterObjectSchemaStmt:
                    n->missing_ok = false;
                    $$ = (Node *)n;
                }
-           | ALTER ROUTINE function_with_argtypes SET SCHEMA name
+           | ALTER ROUTINE procedure_with_argtypes SET SCHEMA name
                {
                    AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
                    n->objectType = OBJECT_ROUTINE;
@@ -9273,7 +9304,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
                    n->newowner = $9;
                    $$ = (Node *)n;
                }
-           | ALTER PROCEDURE function_with_argtypes OWNER TO RoleSpec
+           | ALTER PROCEDURE procedure_with_argtypes OWNER TO RoleSpec
                {
                    AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
                    n->objectType = OBJECT_PROCEDURE;
@@ -9281,7 +9312,7 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
                    n->newowner = $6;
                    $$ = (Node *)n;
                }
-           | ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec
+           | ALTER ROUTINE procedure_with_argtypes OWNER TO RoleSpec
                {
                    AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
                    n->objectType = OBJECT_ROUTINE;
@@ -16218,13 +16249,14 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
 }
 
 /* extractArgTypes()
+ *
  * Given a list of FunctionParameter nodes, extract a list of just the
- * argument types (TypeNames) for input parameters only.  This is what
- * is needed to look up an existing function, which is what is wanted by
- * the productions that use this call.
+ * argument types (TypeNames) for signature parameters only (e.g., only input
+ * parameters for functions).  This is what is needed to look up an existing
+ * function, which is what is wanted by the productions that use this call.
  */
 static List *
-extractArgTypes(List *parameters)
+extractArgTypes(ObjectType objtype, List *parameters)
 {
    List       *result = NIL;
    ListCell   *i;
@@ -16233,7 +16265,7 @@ extractArgTypes(List *parameters)
    {
        FunctionParameter *p = (FunctionParameter *) lfirst(i);
 
-       if (p->mode != FUNC_PARAM_OUT && p->mode != FUNC_PARAM_TABLE)
+       if ((p->mode != FUNC_PARAM_OUT || objtype == OBJECT_PROCEDURE) && p->mode != FUNC_PARAM_TABLE)
            result = lappend(result, p->argType);
    }
    return result;
@@ -16246,7 +16278,7 @@ static List *
 extractAggrArgTypes(List *aggrargs)
 {
    Assert(list_length(aggrargs) == 2);
-   return extractArgTypes((List *) linitial(aggrargs));
+   return extractArgTypes(OBJECT_AGGREGATE, (List *) linitial(aggrargs));
 }
 
 /* makeOrderedSetArgs()
index 78ed85720385cedcd8dc43bb5bc85e895e87c04d..b9efa7729152e8b41e23400e2bb467b2d1ec9f01 100644 (file)
@@ -1233,7 +1233,8 @@ get_func_trftypes(HeapTuple procTup,
  * are set to NULL.  You don't get anything if proargnames is NULL.
  */
 int
-get_func_input_arg_names(Datum proargnames, Datum proargmodes,
+get_func_input_arg_names(char prokind,
+                        Datum proargnames, Datum proargmodes,
                         char ***arg_names)
 {
    ArrayType  *arr;
@@ -1291,6 +1292,7 @@ get_func_input_arg_names(Datum proargnames, Datum proargmodes,
        if (argmodes == NULL ||
            argmodes[i] == PROARGMODE_IN ||
            argmodes[i] == PROARGMODE_INOUT ||
+           (argmodes[i] == PROARGMODE_OUT && prokind == PROKIND_PROCEDURE) ||
            argmodes[i] == PROARGMODE_VARIADIC)
        {
            char       *pname = TextDatumGetCString(argnames[i]);
index b50fa25dbd8600fe0bc17862e46d78b357f74792..268c81089653f15ee07b8437b4b902e514fc4961 100644 (file)
@@ -91,7 +91,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
     * proargtypes
     */
 
-   /* parameter types (excludes OUT params) */
+   /* parameter types (excludes OUT params of functions) */
    oidvector   proargtypes BKI_LOOKUP(pg_type) BKI_FORCE_NOT_NULL;
 
 #ifdef CATALOG_VARLEN
index b047acdc1a85f12da5365003cd4aa27ecaa66233..2f46442087e2a14d9f675edc29ba181a9febdcec 100644 (file)
@@ -172,7 +172,8 @@ extern int  get_func_arg_info(HeapTuple procTup,
                              Oid **p_argtypes, char ***p_argnames,
                              char **p_argmodes);
 
-extern int get_func_input_arg_names(Datum proargnames, Datum proargmodes,
+extern int get_func_input_arg_names(char prokind,
+                                    Datum proargnames, Datum proargmodes,
                                     char ***arg_names);
 
 extern int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes);
index c55c59cbceb3b8280a8174e5c199ebaaf91fa452..a08b9ff795c64d0bac10a10625efe1ce02a5f9c0 100644 (file)
@@ -48,6 +48,24 @@ CALL test_proc6(2, 3, 4);
  6 | 8
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plperl
+AS $$
+my ($a, $b) = @_;
+elog(NOTICE, "a: $a, b: $b");
+return { b => $a * 2 };
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE:  a: 10, b: 
+NOTICE:  _a: 10, _b: 20
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index 2cf5461fefde60eb7ceace76bf5b7fac92e7627c..bbea85fc9f50177e4f405a0f1262043c9f1440d3 100644 (file)
@@ -51,6 +51,26 @@ $$;
 CALL test_proc6(2, 3, 4);
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plperl
+AS $$
+my ($a, $b) = @_;
+elog(NOTICE, "a: $a, b: $b");
+return { b => $a * 2 };
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index d9c88e85c8d8725b15446abc8968198f2ae86144..973857161105ad00772c98064b6a780e820d7a77 100644 (file)
@@ -264,6 +264,25 @@ END
 $$;
 ERROR:  procedure parameter "c" is an output parameter but corresponding argument is not writable
 CONTEXT:  PL/pgSQL function inline_code_block line 5 at CALL
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  RAISE NOTICE 'a: %, b: %', a, b;
+  b := a * 2;
+END;
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE:  a: 10, b: <NULL>
+NOTICE:  _a: 10, _b: 20
 -- transition variable assignment
 TRUNCATE test1;
 CREATE FUNCTION triggerfunc1() RETURNS trigger
index e7f4a5f291d1772de7c808db1ca72e6be52ca77f..344627da956f8ad5e6b7ca31535986091ec7a8ac 100644 (file)
@@ -458,6 +458,7 @@ do_compile(FunctionCallInfo fcinfo,
                /* Remember arguments in appropriate arrays */
                if (argmode == PROARGMODE_IN ||
                    argmode == PROARGMODE_INOUT ||
+                   (argmode == PROARGMODE_OUT && function->fn_prokind == PROKIND_PROCEDURE) ||
                    argmode == PROARGMODE_VARIADIC)
                    in_arg_varnos[num_in_args++] = argvariable->dno;
                if (argmode == PROARGMODE_OUT ||
index 4702bd14d12e89238d6a100c77c88ba3560c6af2..d506809ddbf63ebd819f1a382f0b07d6a04abdd6 100644 (file)
@@ -237,6 +237,27 @@ END
 $$;
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  RAISE NOTICE 'a: %, b: %', a, b;
+  b := a * 2;
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
 -- transition variable assignment
 
 TRUNCATE test1;
index 07ae04e98ba22fd89f3277bb9fe22f463f8babf2..c3f3c8e95e573a93c121551919c5279920084b94 100644 (file)
@@ -52,6 +52,23 @@ CALL test_proc6(2, 3, 4);
  6 | 8
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpythonu
+AS $$
+plpy.notice("a: %s, b: %s" % (a, b))
+return (a * 2,)
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE:  a: 10, b: None
+NOTICE:  _a: 10, _b: 20
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index 9e1583961115982019650ab4ce4b5fe6f43df195..ec47f52e61d64d2fa2aba67d8f433f4cb469a5a2 100644 (file)
@@ -273,7 +273,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
                /* proc->nargs was initialized to 0 above */
                for (i = 0; i < total; i++)
                {
-                   if (modes[i] != PROARGMODE_OUT &&
+                   if ((modes[i] != PROARGMODE_OUT || proc->is_procedure) &&
                        modes[i] != PROARGMODE_TABLE)
                        (proc->nargs)++;
                }
@@ -289,7 +289,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
                Form_pg_type argTypeStruct;
 
                if (modes &&
-                   (modes[i] == PROARGMODE_OUT ||
+                   ((modes[i] == PROARGMODE_OUT && !proc->is_procedure) ||
                     modes[i] == PROARGMODE_TABLE))
                    continue;   /* skip OUT arguments */
 
index 2f792f92bd789bd2b5c9e7db596f62a043699f44..46e89b1a9e1c6fc968b6ad3cf5944e57b2af4287 100644 (file)
@@ -54,6 +54,25 @@ $$;
 CALL test_proc6(2, 3, 4);
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpythonu
+AS $$
+plpy.notice("a: %s, b: %s" % (a, b))
+return (a * 2,)
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index d290c8fbd05c397a9efa23230eb4e106d2134097..f0eb356cf23aca6f8b58719d01035f5405a106c8 100644 (file)
@@ -49,6 +49,23 @@ CALL test_proc6(2, 3, 4);
  6 | 8
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE pltcl
+AS $$
+elog NOTICE "a: $1, b: $2"
+return [list b [expr {$1 * 2}]]
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE:  a: 10, b: 
+NOTICE:  _a: 10, _b: 20
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index 95791d08beea694f3ef7eb8f82034cdfbe8be67d..963277e1fb87b99423a2107a616bfa2577205e12 100644 (file)
@@ -52,6 +52,25 @@ $$;
 CALL test_proc6(2, 3, 4);
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE pltcl
+AS $$
+elog NOTICE "a: $1, b: $2"
+return [list b [expr {$1 * 2}]]
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+  _a := 10; _b := 30;
+  CALL test_proc9(_a, _b);
+  RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
index 211a42cefa039e70fc3e63289f724505081f7758..3838fa2324da40bf05e60700babcd605acf7ee79 100644 (file)
@@ -146,6 +146,19 @@ AS $$
 SELECT a = b;
 $$;
 CALL ptest7(least('a', 'b'), 'a');
+-- OUT parameters
+CREATE PROCEDURE ptest9(OUT a int)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, 'a');
+SELECT 1;
+$$;
+CALL ptest9(NULL);
+ a 
+---
+ 1
+(1 row)
+
 -- various error cases
 CALL version();  -- error: not a procedure
 ERROR:  version() is not a procedure
@@ -165,9 +178,6 @@ CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (
 ERROR:  invalid attribute in procedure definition
 LINE 1: CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT I...
                                                ^
-CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
-ERROR:  procedures cannot have OUT arguments
-HINT:  INOUT arguments are permitted.
 ALTER PROCEDURE ptest1(text) STRICT;
 ERROR:  invalid attribute in procedure definition
 LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
index 89b96d580ffa472b3dfc705e36c100c16eb08429..2ef1c82ceabe2c280e7e9740c180a0dccbd0786e 100644 (file)
@@ -112,6 +112,18 @@ $$;
 CALL ptest7(least('a', 'b'), 'a');
 
 
+-- OUT parameters
+
+CREATE PROCEDURE ptest9(OUT a int)
+LANGUAGE SQL
+AS $$
+INSERT INTO cp_test VALUES (1, 'a');
+SELECT 1;
+$$;
+
+CALL ptest9(NULL);
+
+
 -- various error cases
 
 CALL version();  -- error: not a procedure
@@ -119,7 +131,6 @@ CALL sum(1);  -- error: not a procedure
 
 CREATE PROCEDURE ptestx() LANGUAGE SQL WINDOW AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
 CREATE PROCEDURE ptestx() LANGUAGE SQL STRICT AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
-CREATE PROCEDURE ptestx(OUT a int) LANGUAGE SQL AS $$ INSERT INTO cp_test VALUES (1, 'a') $$;
 
 ALTER PROCEDURE ptest1(text) STRICT;
 ALTER FUNCTION ptest1(text) VOLATILE;  -- error: not a function