On 3/8/18 02:25, Pavel Stehule wrote:
> It looks like some error in this concept. The rules for enabling
> overwriting procedures should modified, so this collision should not be
> done.
> 
> When I using procedure from PL/pgSQL, then it is clear, so I place on
> *OUT position variables. But when I call procedure from top, then I'll
> pass fake parameters to get some result.

What we'll probably want to do here is to make the OUT parameters part
of the identity signature of procedures, unlike in functions.  This
should be a straightforward change, but it will require some legwork in
many parts of the code.

>                if (argmodes && (argmodes[i] == PROARGMODE_INOUT ||
> argmodes[i] == PROARGMODE_OUT))
> +               {
> +                   Param      *param;
> 
> Because PROARGMODE_OUT are disallowed, then this check is little bit
> messy. Please, add some comment.

Fixed.

I discovered another issue, in LANGUAGE SQL procedures.  Currently, if
you make a CALL with an INOUT parameter in an SQL procedure, the output
is thrown away (unless it's the last command).  I would like to keep
open the option of assigning the results by name, like we do in
PL/pgSQL.  So in this patch I have made a change to prohibit calling
procedures with INOUT parameters in LANGUAGE SQL routines (see
check_sql_fn_statements()).  What do you think?

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From 5b9f1506e73826f4f6ff567e54b12c4e232a4263 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@gmx.net>
Date: Mon, 12 Mar 2018 21:39:26 -0400
Subject: [PATCH v4] Support INOUT parameters in procedures

In a top-level CALL, the values of INOUT parameters will be returned as
a result row.  In PL/pgSQL, the values are assigned back to the input
parameters.  In other languages, the same convention as for return a
record from a function is used.  That does not require any code changes
in the PL implementations.

Reviewed-by: Pavel Stehule <pavel.steh...@gmail.com>
---
 doc/src/sgml/plperl.sgml                           |  14 +++
 doc/src/sgml/plpgsql.sgml                          |  16 +++
 doc/src/sgml/plpython.sgml                         |  11 ++
 doc/src/sgml/pltcl.sgml                            |  12 ++
 doc/src/sgml/ref/create_procedure.sgml             |   7 +-
 src/backend/catalog/pg_proc.c                      |   4 +-
 src/backend/commands/functioncmds.c                |  51 +++++++--
 src/backend/executor/functions.c                   |  51 +++++++++
 src/backend/tcop/utility.c                         |   3 +-
 src/backend/utils/fmgr/funcapi.c                   |  11 +-
 src/include/commands/defrem.h                      |   3 +-
 src/include/executor/functions.h                   |   2 +
 src/include/funcapi.h                              |   3 +-
 src/pl/plperl/expected/plperl_call.out             |  25 +++++
 src/pl/plperl/sql/plperl_call.sql                  |  22 ++++
 src/pl/plpgsql/src/expected/plpgsql_call.out       |  89 +++++++++++++++
 .../plpgsql/src/expected/plpgsql_transaction.out   |   2 +-
 src/pl/plpgsql/src/pl_comp.c                       |  10 +-
 src/pl/plpgsql/src/pl_exec.c                       | 125 ++++++++++++++++++++-
 src/pl/plpgsql/src/pl_funcs.c                      |  25 +++++
 src/pl/plpgsql/src/pl_gram.y                       |  38 +++++--
 src/pl/plpgsql/src/pl_scanner.c                    |   1 +
 src/pl/plpgsql/src/plpgsql.h                       |  12 ++
 src/pl/plpgsql/src/sql/plpgsql_call.sql            | 108 ++++++++++++++++++
 src/pl/plpython/expected/plpython_call.out         |  23 ++++
 src/pl/plpython/plpy_exec.c                        |  24 ++--
 src/pl/plpython/sql/plpython_call.sql              |  20 ++++
 src/pl/tcl/expected/pltcl_call.out                 |  26 +++++
 src/pl/tcl/sql/pltcl_call.sql                      |  23 ++++
 src/test/regress/expected/create_procedure.out     |  21 ++++
 src/test/regress/sql/create_procedure.sql          |  19 ++++
 31 files changed, 752 insertions(+), 49 deletions(-)

diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml
index cff7a847de..9295c03db9 100644
--- a/doc/src/sgml/plperl.sgml
+++ b/doc/src/sgml/plperl.sgml
@@ -278,6 +278,20 @@ <title>PL/Perl Functions and Arguments</title>
    hash will be returned as null values.
   </para>
 
+  <para>
+   Similarly, output parameters of procedures can be returned as a hash
+   reference:
+
+<programlisting>
+CREATE PROCEDURE perl_triple(INOUT a integer, INOUT b integer) AS $$
+    my ($a, $b) = @_;
+    return {a =&gt; $a * 3, b =&gt; $b * 3};
+$$ LANGUAGE plperl;
+
+CALL perl_triple(5, 10);
+</programlisting>
+  </para>
+
   <para>
     PL/Perl functions can also return sets of either scalar or
     composite types.  Usually you'll want to return rows one at a
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c1e3c6a19d..6c25116538 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1870,6 +1870,22 @@ <title>Returning From a Procedure</title>
      then <symbol>NULL</symbol> must be returned.  Returning any other value
      will result in an error.
     </para>
+
+    <para>
+     If a procedure has output parameters, then the output values can be
+     assigned to the parameters as if they were variables.  For example:
+<programlisting>
+CREATE PROCEDURE triple(INOUT x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    x := x * 3;
+END;
+$$;
+
+CALL triple(5);
+</programlisting>
+    </para>
    </sect2>
 
    <sect2 id="plpgsql-conditionals">
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index ba79beb743..3b7974690e 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -649,6 +649,17 @@ <title>Composite Types</title>
 $$ LANGUAGE plpythonu;
 
 SELECT * FROM multiout_simple();
+</programlisting>
+   </para>
+
+   <para>
+    Output parameters of procedures are passed back the same way.  For example:
+<programlisting>
+CREATE PROCEDURE python_triple(INOUT a integer, INOUT b integer) AS $$
+return (a * 3, b * 3)
+$$ LANGUAGE plpythonu;
+
+CALL python_triple(5, 10);
 </programlisting>
    </para>
   </sect2>
diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml
index a834ab8862..121260379a 100644
--- a/doc/src/sgml/pltcl.sgml
+++ b/doc/src/sgml/pltcl.sgml
@@ -186,6 +186,18 @@ <title>PL/Tcl Functions and Arguments</title>
 </programlisting>
     </para>
 
+    <para>
+     Output parameters of procedures are returned in the same way, for example:
+
+<programlisting>
+CREATE PROCEDURE tcl_triple(INOUT a integer, INOUT b integer) AS $$
+    return [list a [expr {$1 * 3}] b [expr {$2 * 3}]]
+$$ LANGUAGE pltcl;
+
+CALL tcl_triple(5, 10);
+</programlisting>
+    </para>
+
     <tip>
      <para>
       The result list can be made from an array representation of the
diff --git a/doc/src/sgml/ref/create_procedure.sgml 
b/doc/src/sgml/ref/create_procedure.sgml
index bbf8b03d04..f3c3bb006c 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -96,8 +96,11 @@ <title>Parameters</title>
 
      <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>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.)
       </para>
      </listitem>
     </varlistentry>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 40e579f95d..466ff038e7 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -438,7 +438,8 @@ ProcedureCreate(const char *procedureName,
                        TupleDesc       newdesc;
 
                        olddesc = build_function_result_tupdesc_t(oldtup);
-                       newdesc = 
build_function_result_tupdesc_d(allParameterTypes,
+                       newdesc = build_function_result_tupdesc_d(prokind,
+                                                                               
                          allParameterTypes,
                                                                                
                          parameterModes,
                                                                                
                          parameterNames);
                        if (olddesc == NULL && newdesc == NULL)
@@ -925,6 +926,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
                                                                                
         querytree_sublist);
                        }
 
+                       check_sql_fn_statements(querytree_list);
                        (void) check_sql_fn_retval(funcoid, proc->prorettype,
                                                                           
querytree_list,
                                                                           
NULL, NULL);
diff --git a/src/backend/commands/functioncmds.c 
b/src/backend/commands/functioncmds.c
index b1f87d056e..6267c785db 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -68,6 +68,7 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 #include "utils/tqual.h"
 
 /*
@@ -281,10 +282,11 @@ interpret_function_parameter_list(ParseState *pstate,
 
                if (objtype == OBJECT_PROCEDURE)
                {
-                       if (fp->mode == FUNC_PARAM_OUT || fp->mode == 
FUNC_PARAM_INOUT)
+                       if (fp->mode == FUNC_PARAM_OUT)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                (errmsg("procedures cannot 
have OUT parameters"))));
+                                                (errmsg("procedures cannot 
have OUT parameters"),
+                                                 errhint("INOUT parameters are 
permitted."))));
                }
 
                /* handle input parameters */
@@ -302,7 +304,9 @@ interpret_function_parameter_list(ParseState *pstate,
                /* handle output parameters */
                if (fp->mode != FUNC_PARAM_IN && fp->mode != 
FUNC_PARAM_VARIADIC)
                {
-                       if (outCount == 0)      /* save first output param's 
type */
+                       if (objtype == OBJECT_PROCEDURE)
+                               *requiredResultType = RECORDOID;
+                       else if (outCount == 0) /* save first output param's 
type */
                                *requiredResultType = toid;
                        outCount++;
                }
@@ -1003,12 +1007,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt 
*stmt)
 
        if (stmt->is_procedure)
        {
-               /*
-                * Sometime in the future, procedures might be allowed to return
-                * results; for now, they all return VOID.
-                */
                Assert(!stmt->returnType);
-               prorettype = VOIDOID;
+               prorettype = requiredResultType ? requiredResultType : VOIDOID;
                returnsSet = false;
        }
        else if (stmt->returnType)
@@ -2206,7 +2206,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
  * commits that might occur inside the procedure.
  */
 void
-ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
+ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, 
DestReceiver *dest)
 {
        ListCell   *lc;
        FuncExpr   *fexpr;
@@ -2219,6 +2219,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, 
bool atomic)
        EState     *estate;
        ExprContext *econtext;
        HeapTuple       tp;
+       Datum           retval;
 
        fexpr = stmt->funcexpr;
        Assert(fexpr);
@@ -2285,7 +2286,37 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, 
bool atomic)
                i++;
        }
 
-       FunctionCallInvoke(&fcinfo);
+       retval = FunctionCallInvoke(&fcinfo);
+
+       if (fexpr->funcresulttype == RECORDOID && !fcinfo.isnull)
+       {
+               HeapTupleHeader td;
+               Oid                     tupType;
+               int32           tupTypmod;
+               TupleDesc       retdesc;
+               HeapTupleData rettupdata;
+               TupOutputState *tstate;
+               TupleTableSlot *slot;
+
+               td = DatumGetHeapTupleHeader(retval);
+               tupType = HeapTupleHeaderGetTypeId(td);
+               tupTypmod = HeapTupleHeaderGetTypMod(td);
+               retdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+               rettupdata.t_len = HeapTupleHeaderGetDatumLength(td);
+               ItemPointerSetInvalid(&(rettupdata.t_self));
+               rettupdata.t_tableOid = InvalidOid;
+               rettupdata.t_data = td;
+
+               tstate = begin_tup_output_tupdesc(dest, retdesc);
+
+               slot = ExecStoreTuple(&rettupdata, tstate->slot, InvalidBuffer, 
false);
+               tstate->dest->receiveSlot(slot, tstate->dest);
+
+               end_tup_output(tstate);
+
+               ReleaseTupleDesc(retdesc);
+       }
 
        FreeExecutorState(estate);
 }
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 78bc4ab34b..f336190607 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -721,6 +721,8 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool 
lazyEvalOK)
                                                                          
list_copy(queryTree_sublist));
        }
 
+       check_sql_fn_statements(flat_query_list);
+
        /*
         * Check that the function returns the type it claims to.  Although in
         * simple cases this was already done when the function was defined, we
@@ -1486,6 +1488,55 @@ ShutdownSQLFunction(Datum arg)
        fcache->shutdown_reg = false;
 }
 
+/*
+ * check_sql_fn_statements
+ *
+ * Check statements in an SQL function.  Error out if there is anything that
+ * is not acceptable.
+ */
+void
+check_sql_fn_statements(List *queryTreeList)
+{
+       ListCell   *lc;
+
+       foreach(lc, queryTreeList)
+       {
+               Query      *query = lfirst_node(Query, lc);
+
+               /*
+                * Disallow procedures with output parameters.  The current
+                * implementation would just throw the output values away, 
unless the
+                * statement is the last one.  Per SQL standard, we should 
assign the
+                * output values by name.  By disallowing this here, we 
preserve an
+                * opportunity for future improvement.
+                */
+               if (query->commandType == CMD_UTILITY &&
+                       IsA(query->utilityStmt, CallStmt))
+               {
+                       CallStmt   *stmt = castNode(CallStmt, 
query->utilityStmt);
+                       HeapTuple       tuple;
+                       int                     numargs;
+                       Oid                *argtypes;
+                       char      **argnames;
+                       char       *argmodes;
+                       int                     i;
+
+                       tuple = SearchSysCache1(PROCOID, 
ObjectIdGetDatum(stmt->funcexpr->funcid));
+                       if (!HeapTupleIsValid(tuple))
+                               elog(ERROR, "cache lookup failed for function 
%u", stmt->funcexpr->funcid);
+                       numargs = get_func_arg_info(tuple, &argtypes, 
&argnames, &argmodes);
+                       ReleaseSysCache(tuple);
+
+                       for (i = 0; i < numargs; i++)
+                       {
+                               if (argmodes && (argmodes[i] == 
PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT))
+                                       ereport(ERROR,
+                                                       
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("calling 
procedures with output parameters is not supported in SQL functions")));
+                       }
+               }
+       }
+}
 
 /*
  * check_sql_fn_retval() -- check return value of a list of sql parse trees.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f78efdf359..6effe031f8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -661,7 +661,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
                case T_CallStmt:
                        ExecuteCallStmt(castNode(CallStmt, parsetree), params,
-                                                       (context != 
PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
+                                                       (context != 
PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()),
+                                                       dest);
                        break;
 
                case T_ClusterStmt:
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index c0076bfce3..20f60392af 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1205,7 +1205,8 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
        if (isnull)
                proargnames = PointerGetDatum(NULL);    /* just to be sure */
 
-       return build_function_result_tupdesc_d(proallargtypes,
+       return build_function_result_tupdesc_d(procform->prokind,
+                                                                               
   proallargtypes,
                                                                                
   proargmodes,
                                                                                
   proargnames);
 }
@@ -1218,10 +1219,12 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
  * convenience of ProcedureCreate, which needs to be able to compute the
  * tupledesc before actually creating the function.
  *
- * Returns NULL if there are not at least two OUT or INOUT arguments.
+ * For functions (but not for procedures), returns NULL if there are not at
+ * least two OUT or INOUT arguments.
  */
 TupleDesc
-build_function_result_tupdesc_d(Datum proallargtypes,
+build_function_result_tupdesc_d(char prokind,
+                                                               Datum 
proallargtypes,
                                                                Datum 
proargmodes,
                                                                Datum 
proargnames)
 {
@@ -1311,7 +1314,7 @@ build_function_result_tupdesc_d(Datum proallargtypes,
         * If there is no output argument, or only one, the function does not
         * return tuples.
         */
-       if (numoutargs < 2)
+       if (numoutargs < 2 && prokind != PROKIND_PROCEDURE)
                return NULL;
 
        desc = CreateTemplateTupleDesc(numoutargs, false);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index c829abfea7..8fc9e424cf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -17,6 +17,7 @@
 #include "catalog/objectaddress.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
+#include "tcop/dest.h"
 #include "utils/array.h"
 
 /* commands/dropcmds.c */
@@ -62,7 +63,7 @@ extern void DropTransformById(Oid transformOid);
 extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
                                                   oidvector *proargtypes, Oid 
nspOid);
 extern void ExecuteDoStmt(DoStmt *stmt, bool atomic);
-extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic);
+extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, 
DestReceiver *dest);
 extern Oid     get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool 
missing_ok);
 extern Oid     get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
 extern void interpret_function_parameter_list(ParseState *pstate,
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index e7454ee790..a309809ba8 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -29,6 +29,8 @@ extern SQLFunctionParseInfoPtr 
prepare_sql_fn_parse_info(HeapTuple procedureTupl
 extern void sql_fn_parser_setup(struct ParseState *pstate,
                                        SQLFunctionParseInfoPtr pinfo);
 
+extern void check_sql_fn_statements(List *queryTreeList);
+
 extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
                                        List *queryTreeList,
                                        bool *modifyTargetList,
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index c2da2eb157..01aa208c5e 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -187,7 +187,8 @@ extern int get_func_input_arg_names(Datum proargnames, 
Datum proargmodes,
 extern int     get_func_trftypes(HeapTuple procTup, Oid **p_trftypes);
 extern char *get_func_result_name(Oid functionId);
 
-extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
+extern TupleDesc build_function_result_tupdesc_d(char prokind,
+                                                               Datum 
proallargtypes,
                                                                Datum 
proargmodes,
                                                                Datum 
proargnames);
 extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
diff --git a/src/pl/plperl/expected/plperl_call.out 
b/src/pl/plperl/expected/plperl_call.out
index 4bccfcb7c8..a9dd3e74b4 100644
--- a/src/pl/plperl/expected/plperl_call.out
+++ b/src/pl/plperl/expected/plperl_call.out
@@ -23,6 +23,31 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plperl
+AS $$
+my ($a) = @_;
+return { a => "$a+$a" };
+$$;
+CALL test_proc5('abc');
+    a    
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plperl
+AS $$
+my ($a, $b, $c) = @_;
+return { b => $b * $a, c => $c * $a };
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c 
+---+---
+ 6 | 8
+(1 row)
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/pl/plperl/sql/plperl_call.sql 
b/src/pl/plperl/sql/plperl_call.sql
index bd2b63b418..64a09e1f31 100644
--- a/src/pl/plperl/sql/plperl_call.sql
+++ b/src/pl/plperl/sql/plperl_call.sql
@@ -29,6 +29,28 @@ CREATE PROCEDURE test_proc3(x int)
 SELECT * FROM test1;
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plperl
+AS $$
+my ($a) = @_;
+return { a => "$a+$a" };
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plperl
+AS $$
+my ($a, $b, $c) = @_;
+return { b => $b * $a, c => $c * $a };
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out 
b/src/pl/plpgsql/src/expected/plpgsql_call.out
index 2f3adcd8d8..1d042ae52f 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -53,6 +53,95 @@ SELECT * FROM test1;
  66
 (2 rows)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    a := a || '+' || a;
+END;
+$$;
+CALL test_proc5('abc');
+    a    
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    b := b * a;
+    c := c * a;
+END;
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c 
+---+---
+ 6 | 8
+(1 row)
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    CALL test_proc6(2, x, y);
+    RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+INFO:  x = 6, y = 8
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    CALL test_proc6(2, x + 1, y);  -- error
+    RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+ERROR:  argument 2 is an output parameter but is not writable
+CONTEXT:  PL/pgSQL function inline_code_block line 6 at CALL
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    FOR i IN 1..5 LOOP
+        CALL test_proc6(i, x, y);
+        RAISE INFO 'x = %, y = %', x, y;
+    END LOOP;
+END;
+$$;
+INFO:  x = 3, y = 4
+INFO:  x = 6, y = 8
+INFO:  x = 18, y = 24
+INFO:  x = 72, y = 96
+INFO:  x = 360, y = 480
+-- recursive with output parameters
+CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+IF x > 1 THEN
+    a := x / 10;
+    b := x / 2;
+    CALL test_proc7(b::int, a, b);
+END IF;
+END;
+$$;
+CALL test_proc7(100, -1, -1);
+ a | b 
+---+---
+ 0 | 1
+(1 row)
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc3;
 DROP PROCEDURE test_proc4;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out 
b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 8ec22c646c..ce66487137 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -98,7 +98,7 @@ SELECT transaction_test3();
 ERROR:  invalid transaction termination
 CONTEXT:  PL/pgSQL function transaction_test1() line 6 at COMMIT
 SQL statement "CALL transaction_test1()"
-PL/pgSQL function transaction_test3() line 3 at SQL statement
+PL/pgSQL function transaction_test3() line 3 at CALL
 SELECT * FROM test1;
  a | b 
 ---+---
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 391ec41b80..b1a0c1cc4f 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -475,11 +475,11 @@ do_compile(FunctionCallInfo fcinfo,
                        /*
                         * 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.
+                        * holds all of them.  Procedures return a row even for 
one OUT
+                        * parameter.
                         */
-                       if (num_out_args == 1)
-                               function->out_param_varno = 
out_arg_variables[0]->dno;
-                       else if (num_out_args > 1)
+                       if (num_out_args > 1 ||
+                               (num_out_args == 1 && function->fn_prokind == 
PROKIND_PROCEDURE))
                        {
                                PLpgSQL_row *row = 
build_row_from_vars(out_arg_variables,
                                                                                
                           num_out_args);
@@ -487,6 +487,8 @@ do_compile(FunctionCallInfo fcinfo,
                                plpgsql_adddatum((PLpgSQL_datum *) row);
                                function->out_param_varno = row->dno;
                        }
+                       else if (num_out_args == 1)
+                               function->out_param_varno = 
out_arg_variables[0]->dno;
 
                        /*
                         * Check for a polymorphic returntype. If found, use 
the actual
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 489484f184..0841a81e87 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_type.h"
 #include "executor/execExpr.h"
 #include "executor/spi.h"
+#include "executor/spi_priv.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -40,6 +41,7 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 #include "utils/typcache.h"
 
 #include "plpgsql.h"
@@ -253,6 +255,8 @@ static int exec_stmt_assign(PLpgSQL_execstate *estate,
                                 PLpgSQL_stmt_assign *stmt);
 static int exec_stmt_perform(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_perform *stmt);
+static int exec_stmt_call(PLpgSQL_execstate *estate,
+                                 PLpgSQL_stmt_call *stmt);
 static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
@@ -1901,6 +1905,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) 
stmt);
                        break;
 
+               case PLPGSQL_STMT_CALL:
+                       rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
+                       break;
+
                case PLPGSQL_STMT_GETDIAG:
                        rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) 
stmt);
                        break;
@@ -2041,6 +2049,121 @@ exec_stmt_perform(PLpgSQL_execstate *estate, 
PLpgSQL_stmt_perform *stmt)
        return PLPGSQL_RC_OK;
 }
 
+/*
+ * exec_stmt_call
+ */
+static int
+exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
+{
+       PLpgSQL_expr *expr = stmt->expr;
+       ParamListInfo paramLI;
+       int                     rc;
+
+       if (expr->plan == NULL)
+               exec_prepare_plan(estate, expr, 0);
+
+       paramLI = setup_param_list(estate, expr);
+
+       rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+                                                                               
 estate->readonly_func, 0);
+
+       if (rc < 0)
+               elog(ERROR, "SPI_execute_plan_with_paramlist failed executing 
query \"%s\": %s",
+                        expr->query, SPI_result_code_string(rc));
+
+       if (SPI_processed == 1)
+       {
+               SPITupleTable *tuptab = SPI_tuptable;
+
+               /*
+                * Construct a dummy target row based on the INOUT parameters 
of the
+                * procedure call.
+                */
+               if (!stmt->target)
+               {
+                       Node       *node;
+                       ListCell   *lc;
+                       FuncExpr   *funcexpr;
+                       int                     i;
+                       HeapTuple       tuple;
+                       int                     numargs;
+                       Oid                *argtypes;
+                       char      **argnames;
+                       char       *argmodes;
+                       MemoryContext oldcontext;
+                       PLpgSQL_row *row;
+                       int                     nfields;
+
+                       /*
+                        * Get the original CallStmt
+                        */
+                       node = linitial_node(Query, ((CachedPlanSource *) 
linitial(expr->plan->plancache_list))->query_list)->utilityStmt;
+                       if (!IsA(node, CallStmt))
+                               elog(ERROR, "returned row from not a CallStmt");
+
+                       funcexpr = castNode(CallStmt, node)->funcexpr;
+
+                       /*
+                        * Get the parameter modes
+                        */
+                       tuple = SearchSysCache1(PROCOID, 
ObjectIdGetDatum(funcexpr->funcid));
+                       if (!HeapTupleIsValid(tuple))
+                               elog(ERROR, "cache lookup failed for function 
%u", funcexpr->funcid);
+                       numargs = get_func_arg_info(tuple, &argtypes, 
&argnames, &argmodes);
+                       ReleaseSysCache(tuple);
+
+                       Assert(numargs == list_length(funcexpr->args));
+
+                       /*
+                        * Construct row
+                        */
+                       oldcontext = 
MemoryContextSwitchTo(estate->func->fn_cxt);
+
+                       row = palloc0(sizeof(*row));
+                       row->dtype = PLPGSQL_DTYPE_ROW;
+                       row->lineno = -1;
+                       row->varnos = palloc(sizeof(int) * FUNC_MAX_ARGS);
+
+                       nfields = 0;
+                       i = 0;
+                       foreach (lc, funcexpr->args)
+                       {
+                               Node *n = lfirst(lc);
+
+                               if (argmodes && argmodes[i] == PROARGMODE_INOUT)
+                               {
+                                       Param      *param;
+
+                                       if (!IsA(n, Param))
+                                               ereport(ERROR,
+                                                               
(errcode(ERRCODE_SYNTAX_ERROR),
+                                                                
errmsg("argument %d is an output parameter but is not writable", i + 1)));
+
+                                       param = castNode(Param, n);
+                                       /* paramid is offset by 1 (see 
make_datum_param()) */
+                                       row->varnos[nfields++] = param->paramid 
- 1;
+                               }
+                               i++;
+                       }
+
+                       row->nfields = nfields;
+
+                       MemoryContextSwitchTo(oldcontext);
+
+                       stmt->target = (PLpgSQL_variable *) row;
+               }
+
+               exec_move_row(estate, stmt->target, tuptab->vals[0], 
tuptab->tupdesc);
+       }
+       else if (SPI_processed > 1)
+               elog(ERROR, "procedure call returned more than one row");
+
+       exec_eval_cleanup(estate);
+       SPI_freetuptable(SPI_tuptable);
+
+       return PLPGSQL_RC_OK;
+}
+
 /* ----------
  * exec_stmt_getdiag                                   Put internal PG 
information into
  *                                                                             
specified variables.
@@ -6763,7 +6886,7 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
                return;
        }
 
-       elog(ERROR, "unsupported target");
+       elog(ERROR, "unsupported target type: %d", target->dtype);
 }
 
 /*
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index b986fc39b3..39d6a54663 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -284,6 +284,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
                        return "CLOSE";
                case PLPGSQL_STMT_PERFORM:
                        return "PERFORM";
+               case PLPGSQL_STMT_CALL:
+                       return "CALL";
                case PLPGSQL_STMT_COMMIT:
                        return "COMMIT";
                case PLPGSQL_STMT_ROLLBACK:
@@ -367,6 +369,7 @@ static void free_open(PLpgSQL_stmt_open *stmt);
 static void free_fetch(PLpgSQL_stmt_fetch *stmt);
 static void free_close(PLpgSQL_stmt_close *stmt);
 static void free_perform(PLpgSQL_stmt_perform *stmt);
+static void free_call(PLpgSQL_stmt_call *stmt);
 static void free_commit(PLpgSQL_stmt_commit *stmt);
 static void free_rollback(PLpgSQL_stmt_rollback *stmt);
 static void free_expr(PLpgSQL_expr *expr);
@@ -449,6 +452,9 @@ free_stmt(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_PERFORM:
                        free_perform((PLpgSQL_stmt_perform *) stmt);
                        break;
+               case PLPGSQL_STMT_CALL:
+                       free_call((PLpgSQL_stmt_call *) stmt);
+                       break;
                case PLPGSQL_STMT_COMMIT:
                        free_commit((PLpgSQL_stmt_commit *) stmt);
                        break;
@@ -602,6 +608,12 @@ free_perform(PLpgSQL_stmt_perform *stmt)
        free_expr(stmt->expr);
 }
 
+static void
+free_call(PLpgSQL_stmt_call *stmt)
+{
+       free_expr(stmt->expr);
+}
+
 static void
 free_commit(PLpgSQL_stmt_commit *stmt)
 {
@@ -805,6 +817,7 @@ static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
 static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
 static void dump_close(PLpgSQL_stmt_close *stmt);
 static void dump_perform(PLpgSQL_stmt_perform *stmt);
+static void dump_call(PLpgSQL_stmt_call *stmt);
 static void dump_commit(PLpgSQL_stmt_commit *stmt);
 static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
 static void dump_expr(PLpgSQL_expr *expr);
@@ -897,6 +910,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_PERFORM:
                        dump_perform((PLpgSQL_stmt_perform *) stmt);
                        break;
+               case PLPGSQL_STMT_CALL:
+                       dump_call((PLpgSQL_stmt_call *) stmt);
+                       break;
                case PLPGSQL_STMT_COMMIT:
                        dump_commit((PLpgSQL_stmt_commit *) stmt);
                        break;
@@ -1275,6 +1291,15 @@ dump_perform(PLpgSQL_stmt_perform *stmt)
        printf("\n");
 }
 
+static void
+dump_call(PLpgSQL_stmt_call *stmt)
+{
+       dump_ind();
+       printf("CALL expr = ");
+       dump_expr(stmt->expr);
+       printf("\n");
+}
+
 static void
 dump_commit(PLpgSQL_stmt_commit *stmt)
 {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 9fcf2424da..d69e66d00a 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -197,7 +197,7 @@ static      void                    
check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <stmt>   proc_stmt pl_block
 %type <stmt>   stmt_assign stmt_if stmt_loop stmt_while stmt_exit
 %type <stmt>   stmt_return stmt_raise stmt_assert stmt_execsql
-%type <stmt>   stmt_dynexecute stmt_for stmt_perform stmt_getdiag
+%type <stmt>   stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
 %type <stmt>   stmt_open stmt_fetch stmt_move stmt_close stmt_null
 %type <stmt>   stmt_commit stmt_rollback
 %type <stmt>   stmt_case stmt_foreach_a
@@ -257,6 +257,7 @@ static      void                    
check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>       K_BACKWARD
 %token <keyword>       K_BEGIN
 %token <keyword>       K_BY
+%token <keyword>       K_CALL
 %token <keyword>       K_CASE
 %token <keyword>       K_CLOSE
 %token <keyword>       K_COLLATE
@@ -872,6 +873,8 @@ proc_stmt           : pl_block ';'
                                                { $$ = $1; }
                                | stmt_perform
                                                { $$ = $1; }
+                               | stmt_call
+                                               { $$ = $1; }
                                | stmt_getdiag
                                                { $$ = $1; }
                                | stmt_open
@@ -903,6 +906,20 @@ stmt_perform       : K_PERFORM expr_until_semi
                                        }
                                ;
 
+stmt_call              : K_CALL
+                                       {
+                                               PLpgSQL_stmt_call *new;
+
+                                               new = 
palloc0(sizeof(PLpgSQL_stmt_call));
+                                               new->cmd_type = 
PLPGSQL_STMT_CALL;
+                                               new->lineno = 
plpgsql_location_to_lineno(@1);
+                                               new->expr = read_sql_stmt("CALL 
");
+
+                                               $$ = (PLpgSQL_stmt *)new;
+
+                                       }
+                               ;
+
 stmt_assign            : assign_var assign_operator expr_until_semi
                                        {
                                                PLpgSQL_stmt_assign *new;
@@ -2401,6 +2418,7 @@ unreserved_keyword        :
                                | K_ARRAY
                                | K_ASSERT
                                | K_BACKWARD
+                               | K_CALL
                                | K_CLOSE
                                | K_COLLATE
                                | K_COLUMN
@@ -3129,15 +3147,6 @@ make_return_stmt(int location)
                                         errhint("Use RETURN NEXT or RETURN 
QUERY."),
                                         parser_errposition(yylloc)));
        }
-       else if (plpgsql_curr_compile->out_param_varno >= 0)
-       {
-               if (yylex() != ';')
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("RETURN cannot have a parameter 
in function with OUT parameters"),
-                                        parser_errposition(yylloc)));
-               new->retvarno = plpgsql_curr_compile->out_param_varno;
-       }
        else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
        {
                if (yylex() != ';')
@@ -3154,6 +3163,15 @@ make_return_stmt(int location)
                                                 parser_errposition(yylloc)));
                }
        }
+       else if (plpgsql_curr_compile->out_param_varno >= 0)
+       {
+               if (yylex() != ';')
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("RETURN cannot have a parameter 
in function with OUT parameters"),
+                                        parser_errposition(yylloc)));
+               new->retvarno = plpgsql_curr_compile->out_param_varno;
+       }
        else
        {
                /*
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 12a3e6b818..65774f9902 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -102,6 +102,7 @@ static const ScanKeyword unreserved_keywords[] = {
        PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
        PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
        PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+       PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
        PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
        PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
        PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index dd59036de0..f7619a63f9 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -125,6 +125,7 @@ typedef enum PLpgSQL_stmt_type
        PLPGSQL_STMT_FETCH,
        PLPGSQL_STMT_CLOSE,
        PLPGSQL_STMT_PERFORM,
+       PLPGSQL_STMT_CALL,
        PLPGSQL_STMT_COMMIT,
        PLPGSQL_STMT_ROLLBACK
 } PLpgSQL_stmt_type;
@@ -508,6 +509,17 @@ typedef struct PLpgSQL_stmt_perform
        PLpgSQL_expr *expr;
 } PLpgSQL_stmt_perform;
 
+/*
+ * CALL statement
+ */
+typedef struct PLpgSQL_stmt_call
+{
+       PLpgSQL_stmt_type cmd_type;
+       int                     lineno;
+       PLpgSQL_expr *expr;
+       PLpgSQL_variable *target;
+} PLpgSQL_stmt_call;
+
 /*
  * COMMIT statement
  */
diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql 
b/src/pl/plpgsql/src/sql/plpgsql_call.sql
index e580e5fea0..1fc13ddc5e 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -55,6 +55,114 @@ CREATE PROCEDURE test_proc4(y int)
 SELECT * FROM test1;
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    a := a || '+' || a;
+END;
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    b := b * a;
+    c := c * a;
+END;
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    CALL test_proc6(2, x, y);
+    RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    CALL test_proc6(2, x + 1, y);  -- error
+    RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+    x int := 3;
+    y int := 4;
+BEGIN
+    FOR i IN 1..5 LOOP
+        CALL test_proc6(i, x, y);
+        RAISE INFO 'x = %, y = %', x, y;
+    END LOOP;
+END;
+$$;
+
+
+-- recursive with output parameters
+
+CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+IF x > 1 THEN
+    a := x / 10;
+    b := x / 2;
+    CALL test_proc7(b::int, a, b);
+END IF;
+END;
+$$;
+
+CALL test_proc7(100, -1, -1);
+
+
+-- transition variable assignment
+
+TRUNCATE test1;
+
+CREATE FUNCTION f() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    z int := 0;
+BEGIN
+    --NEW.a := NEW.a * 2;
+    CALL test_proc6(2, NEW.a, NEW.a);
+    RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER t BEFORE INSERT ON test1 EXECUTE PROCEDURE f();
+
+INSERT INTO test1 VALUES (1), (2), (3);
+
+UPDATE test1 SET a = 22 WHERE a = 2;
+
+SELECT * FROM test1 ORDER BY a;
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc3;
 DROP PROCEDURE test_proc4;
diff --git a/src/pl/plpython/expected/plpython_call.out 
b/src/pl/plpython/expected/plpython_call.out
index 90785343b6..dff15743aa 100644
--- a/src/pl/plpython/expected/plpython_call.out
+++ b/src/pl/plpython/expected/plpython_call.out
@@ -29,6 +29,29 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpythonu
+AS $$
+return [a + '+' + a]
+$$;
+CALL test_proc5('abc');
+    a    
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpythonu
+AS $$
+return (b * a, c * a)
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c 
+---+---
+ 6 | 8
+(1 row)
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index 1e0f3d9d3a..7c8c7dee87 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -204,21 +204,19 @@ PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure 
*proc)
                 * return value as a special "void datum" rather than NULL (as 
is the
                 * case for non-void-returning functions).
                 */
-               if (proc->is_procedure)
+               if (proc->result.typoid == VOIDOID)
                {
                        if (plrv != Py_None)
-                               ereport(ERROR,
-                                               
(errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("PL/Python procedure 
did not return None")));
-                       fcinfo->isnull = false;
-                       rv = (Datum) 0;
-               }
-               else if (proc->result.typoid == VOIDOID)
-               {
-                       if (plrv != Py_None)
-                               ereport(ERROR,
-                                               
(errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("PL/Python function 
with return type \"void\" did not return None")));
+                       {
+                               if (proc->is_procedure)
+                                       ereport(ERROR,
+                                                       
(errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("PL/Python 
procedure did not return None")));
+                               else
+                                       ereport(ERROR,
+                                                       
(errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("PL/Python 
function with return type \"void\" did not return None")));
+                       }
 
                        fcinfo->isnull = false;
                        rv = (Datum) 0;
diff --git a/src/pl/plpython/sql/plpython_call.sql 
b/src/pl/plpython/sql/plpython_call.sql
index 3fb74de5f0..b71ab284dc 100644
--- a/src/pl/plpython/sql/plpython_call.sql
+++ b/src/pl/plpython/sql/plpython_call.sql
@@ -34,6 +34,26 @@ CREATE PROCEDURE test_proc3(x int)
 SELECT * FROM test1;
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpythonu
+AS $$
+return [a + '+' + a]
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpythonu
+AS $$
+return (b * a, c * a)
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/pl/tcl/expected/pltcl_call.out 
b/src/pl/tcl/expected/pltcl_call.out
index 7221a37ad0..db286522c4 100644
--- a/src/pl/tcl/expected/pltcl_call.out
+++ b/src/pl/tcl/expected/pltcl_call.out
@@ -23,6 +23,32 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- OUT parameters
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE pltcl
+AS $$
+set aa [concat $1 "+" $1]
+return [list a $aa]
+$$;
+CALL test_proc5('abc');
+     a     
+-----------
+ abc + abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE pltcl
+AS $$
+set bb [expr $2 * $1]
+set cc [expr $3 * $1]
+return [list b $bb c $cc]
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c 
+---+---
+ 6 | 8
+(1 row)
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/pl/tcl/sql/pltcl_call.sql b/src/pl/tcl/sql/pltcl_call.sql
index ef1f540f50..d6e350872d 100644
--- a/src/pl/tcl/sql/pltcl_call.sql
+++ b/src/pl/tcl/sql/pltcl_call.sql
@@ -29,6 +29,29 @@ CREATE PROCEDURE test_proc3(x int)
 SELECT * FROM test1;
 
 
+-- OUT parameters
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE pltcl
+AS $$
+set aa [concat $1 "+" $1]
+return [list a $aa]
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE pltcl
+AS $$
+set bb [expr $2 * $1]
+set cc [expr $3 * $1]
+return [list b $bb c $cc]
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc2;
 DROP PROCEDURE test_proc3;
diff --git a/src/test/regress/expected/create_procedure.out 
b/src/test/regress/expected/create_procedure.out
index 182b325ea1..6f217a674f 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -71,6 +71,26 @@ SELECT * FROM cp_test;
  1 | b
 (2 rows)
 
+-- output parameters
+CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
+LANGUAGE SQL
+AS $$
+SELECT 1, 2;
+$$;
+CALL ptest4a(NULL, NULL);
+ a | b 
+---+---
+ 1 | 2
+(1 row)
+
+CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
+LANGUAGE SQL
+AS $$
+CALL ptest4a(a, b);  -- error, not supported
+$$;
+ERROR:  calling procedures with output parameters is not supported in SQL 
functions
+CONTEXT:  SQL function "ptest4b"
+DROP PROCEDURE ptest4a;
 -- various error cases
 CALL version();  -- error: not a procedure
 ERROR:  version() is not a procedure
@@ -91,6 +111,7 @@ 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 parameters
+HINT:  INOUT parameters are permitted.
 ALTER PROCEDURE ptest1(text) STRICT;
 ERROR:  invalid attribute in procedure definition
 LINE 1: ALTER PROCEDURE ptest1(text) STRICT;
diff --git a/src/test/regress/sql/create_procedure.sql 
b/src/test/regress/sql/create_procedure.sql
index 52318bf2a6..31b1db98f5 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -46,6 +46,25 @@ CREATE PROCEDURE ptest3(y text)
 SELECT * FROM cp_test;
 
 
+-- output parameters
+
+CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
+LANGUAGE SQL
+AS $$
+SELECT 1, 2;
+$$;
+
+CALL ptest4a(NULL, NULL);
+
+CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
+LANGUAGE SQL
+AS $$
+CALL ptest4a(a, b);  -- error, not supported
+$$;
+
+DROP PROCEDURE ptest4a;
+
+
 -- various error cases
 
 CALL version();  -- error: not a procedure

base-commit: 4a4e2442a7f7c1434e86dd290cdb3704cfebb24c
-- 
2.16.2

Reply via email to