Support INOUT arguments in procedures
authorPeter Eisentraut <peter_e@gmx.net>
Wed, 14 Mar 2018 15:47:21 +0000 (11:47 -0400)
committerPeter Eisentraut <peter_e@gmx.net>
Wed, 14 Mar 2018 16:07:28 +0000 (12:07 -0400)
In a top-level CALL, the values of INOUT arguments will be returned as a
result row.  In PL/pgSQL, the values are assigned back to the input
arguments.  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.stehule@gmail.com>
32 files changed:
doc/src/sgml/plperl.sgml
doc/src/sgml/plpgsql.sgml
doc/src/sgml/plpython.sgml
doc/src/sgml/pltcl.sgml
doc/src/sgml/ref/call.sgml
doc/src/sgml/ref/create_procedure.sgml
src/backend/catalog/pg_proc.c
src/backend/commands/functioncmds.c
src/backend/executor/functions.c
src/backend/tcop/utility.c
src/backend/utils/fmgr/funcapi.c
src/include/commands/defrem.h
src/include/executor/functions.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/expected/plpgsql_transaction.out
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_gram.y
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/sql/plpgsql_call.sql
src/pl/plpython/expected/plpython_call.out
src/pl/plpython/plpy_exec.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 cff7a847deebf2ae686eb34753f3c04640b35f93..518a86459adcedb3802efb0ff52b48cdc37d5c7e 100644 (file)
@@ -278,6 +278,20 @@ SELECT * FROM perl_row();
    hash will be returned as null values.
   </para>
 
+  <para>
+   Similarly, output arguments 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
index c1e3c6a19d8c1ba5a794ad412800dd16e1dd989b..6c25116538a30ded0bc4a53e38ef9ed88dab1b41 100644 (file)
@@ -1870,6 +1870,22 @@ SELECT * FROM get_available_flightid(CURRENT_DATE);
      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">
index ba79beb743770fbd4bc119be80431826068c9c87..3b7974690edc4b80bc43fabb8101eaf5c5350ef3 100644 (file)
@@ -649,6 +649,17 @@ return (1, 2)
 $$ 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>
index a834ab8862b270c5bf5b219190eedba6a81e540e..01f6207d363201cac6ef6d48d6b128151f414c70 100644 (file)
@@ -186,6 +186,18 @@ $$ LANGUAGE pltcl;
 </programlisting>
     </para>
 
+    <para>
+     Output arguments 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
index d45e3ec22e9cc6ff627cc8beb0fdd1536afb6cdf..7418e19eeba81e592a83bc0a1e81b17e0f8526e8 100644 (file)
@@ -31,6 +31,10 @@ CALL <replaceable class="parameter">name</replaceable> ( [ <replaceable class="p
   <para>
    <command>CALL</command> executes a procedure.
   </para>
+
+  <para>
+   If the procedure has output arguments, then a result row will be returned.
+  </para>
  </refsect1>
 
  <refsect1>
index bbf8b03d04e822091a9f19fa348f9876abca366e..f3c3bb006cf52a50c74cc86513741c69acd96821 100644 (file)
@@ -96,8 +96,11 @@ CREATE [ OR REPLACE ] PROCEDURE
 
      <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>
index 40e579f95dc5c30366a8be4e7b319ad86ba403f7..466ff038e7a43e18997cd92cf2af86724290e279 100644 (file)
@@ -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);
index b1f87d056e518e66abb9f03790c618008d1e4737..86fa8c0dd7415e56893910b019da64241a2bd900 100644 (file)
@@ -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 arguments"),
+                                                 errhint("INOUT arguments 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,51 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
                i++;
        }
 
-       FunctionCallInvoke(&fcinfo);
+       retval = FunctionCallInvoke(&fcinfo);
+
+       if (fexpr->funcresulttype == VOIDOID)
+       {
+               /* do nothing */
+       }
+       else if (fexpr->funcresulttype == RECORDOID)
+       {
+               /*
+                * send tuple to client
+                */
+
+               HeapTupleHeader td;
+               Oid                     tupType;
+               int32           tupTypmod;
+               TupleDesc       retdesc;
+               HeapTupleData rettupdata;
+               TupOutputState *tstate;
+               TupleTableSlot *slot;
+
+               if (fcinfo.isnull)
+                       elog(ERROR, "procedure returned null record");
+
+               td = DatumGetHeapTupleHeader(retval);
+               tupType = HeapTupleHeaderGetTypeId(td);
+               tupTypmod = HeapTupleHeaderGetTypMod(td);
+               retdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+               tstate = begin_tup_output_tupdesc(dest, retdesc);
+
+               rettupdata.t_len = HeapTupleHeaderGetDatumLength(td);
+               ItemPointerSetInvalid(&(rettupdata.t_self));
+               rettupdata.t_tableOid = InvalidOid;
+               rettupdata.t_data = td;
+
+               slot = ExecStoreTuple(&rettupdata, tstate->slot, InvalidBuffer, false);
+               tstate->dest->receiveSlot(slot, tstate->dest);
+
+               end_tup_output(tstate);
+
+               ReleaseTupleDesc(retdesc);
+       }
+       else
+               elog(ERROR, "unexpected result type for procedure: %u",
+                        fexpr->funcresulttype);
 
        FreeExecutorState(estate);
 }
index 78bc4ab34bdec2dc6808d6c6385bb9e6f2698aa9..1c00ac9588f61adcde3e8c55a881ab8b0eb7c8ac 100644 (file)
@@ -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 arguments.  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 arguments is not supported in SQL functions")));
+                       }
+               }
+       }
+}
 
 /*
  * check_sql_fn_retval() -- check return value of a list of sql parse trees.
index f78efdf359a40e212bacbd1ee406424b8909f6d6..6effe031f858ceec459e1965e1395f41e82fc3de 100644 (file)
@@ -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:
index c0076bfce3f6569e2da23718d772e67ab4a6276d..20f60392afee149727f51d9b332168b18eaf0b5c 100644 (file)
@@ -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);
index c829abfea7e60cef754620d761157f94b7b49491..8fc9e424cfc33891fea2fd787b82cc437d4003cd 100644 (file)
@@ -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,
index e7454ee7906e47903e6c2d52f5c4cafd0e3e77ba..a309809ba84bc530f796e4be3d0fd456d718e1d6 100644 (file)
@@ -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,
index c2da2eb157907254a877b714dbba16ebac22442a..01aa208c5eebf134962ce702b6969556c63fca68 100644 (file)
@@ -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);
index 4bccfcb7c82e38316aeb808d2a54685aa3357082..c55c59cbceb3b8280a8174e5c199ebaaf91fa452 100644 (file)
@@ -23,6 +23,31 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- output arguments
+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;
index bd2b63b4185e5300104b40f35f03622dfd6c0ccb..2cf5461fefde60eb7ceace76bf5b7fac92e7627c 100644 (file)
@@ -29,6 +29,28 @@ CALL test_proc3(55);
 SELECT * FROM test1;
 
 
+-- output arguments
+
+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;
index 2f3adcd8d8211e2b9ee010d9e55fb9a5febccc4c..1e94a44f2bb238a2a2dda37618e4d386ddbfeaf3 100644 (file)
@@ -53,6 +53,118 @@ SELECT * FROM test1;
  66
 (2 rows)
 
+-- output arguments
+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 argument 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 arguments
+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)
+
+-- transition variable assignment
+TRUNCATE test1;
+CREATE FUNCTION triggerfunc1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    z int := 0;
+BEGIN
+    CALL test_proc6(2, NEW.a, NEW.a);
+    RETURN NEW;
+END;
+$$;
+CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
+INSERT INTO test1 VALUES (1), (2), (3);
+UPDATE test1 SET a = 22 WHERE a = 2;
+SELECT * FROM test1 ORDER BY a;
+ a  
+----
+  1
+  3
+ 22
+(3 rows)
+
 DROP PROCEDURE test_proc1;
 DROP PROCEDURE test_proc3;
 DROP PROCEDURE test_proc4;
index 8ec22c646c2adf183ae4654cef7f7e409d07804f..ce6648713702fd96a7874340c9717a1af8c0da82 100644 (file)
@@ -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 
 ---+---
index 391ec41b8027d6c5c180a4f9b8c833845154d3ad..b1a0c1cc4f3d0360375d06ce0591753f06095294 100644 (file)
@@ -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
index 489484f184c4b57a0b2860efd083b57bc5e08c02..827e44019d8f03edae98a85d6e53c0e91eae49e5 100644 (file)
@@ -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 output arguments 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 argument 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 argument 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);
 }
 
 /*
index b986fc39b3869ca9874a97446f7a75331261564b..39d6a546632f9b4ff48347c859bfc8988b40328a 100644 (file)
@@ -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)
 {
index 9fcf2424daeeda1241130b9e191248e9f59936d9..4c80936678f9378d637d6005e26beaa2ffc23de9 100644 (file)
@@ -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,19 @@ 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 +2417,7 @@ unreserved_keyword        :
                                | K_ARRAY
                                | K_ASSERT
                                | K_BACKWARD
+                               | K_CALL
                                | K_CLOSE
                                | K_COLLATE
                                | K_COLUMN
@@ -3129,15 +3146,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 +3162,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
        {
                /*
index 12a3e6b818f7eeaaa4b143aa09742f884575e2e0..65774f9902842b14bb414e5ad9248dd0d04ca846 100644 (file)
@@ -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)
index dd59036de096b0d7f9732cddf6f7b3611cced212..f7619a63f9bc0f3345802244dda0381d62806d15 100644 (file)
@@ -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
  */
index e580e5fea0728dd398812aede7896dc06c986f67..f1eed9975a1acfb0a81951e5494a813dd219dbcb 100644 (file)
@@ -55,6 +55,113 @@ CALL test_proc4(66);
 SELECT * FROM test1;
 
 
+-- output arguments
+
+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 arguments
+
+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 triggerfunc1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+    z int := 0;
+BEGIN
+    CALL test_proc6(2, NEW.a, NEW.a);
+    RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
+
+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;
index 90785343b6f162abb6de87e6cbb72a52bf375900..07ae04e98ba22fd89f3277bb9fe22f463f8babf2 100644 (file)
@@ -29,6 +29,29 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- output arguments
+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;
index 1e0f3d9d3aeccb14fefea2d67f5c8e63e873ecc2..7c8c7dee87c9f034a334877f5416c042e2997a23 100644 (file)
@@ -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;
index 3fb74de5f07d0d3c2cdd8514db195101b3bdf0d1..2f792f92bd789bd2b5c9e7db596f62a043699f44 100644 (file)
@@ -34,6 +34,26 @@ CALL test_proc3(55);
 SELECT * FROM test1;
 
 
+-- output arguments
+
+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;
index 7221a37ad0b8d776ec5b3ab6ac1bcf4f0f31b17c..d290c8fbd05c397a9efa23230eb4e106d2134097 100644 (file)
@@ -23,6 +23,32 @@ SELECT * FROM test1;
  55
 (1 row)
 
+-- output arguments
+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;
index ef1f540f50cc68e16e935aa84ca0acc559862d55..95791d08beea694f3ef7eb8f82034cdfbe8be67d 100644 (file)
@@ -29,6 +29,29 @@ CALL test_proc3(55);
 SELECT * FROM test1;
 
 
+-- output arguments
+
+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;
index 182b325ea1cf2b42565f69b21e88e242d4ca0ae5..6ff7e4ba04e2031048920f8464e13159ec806ca6 100644 (file)
@@ -71,6 +71,26 @@ SELECT * FROM cp_test;
  1 | b
 (2 rows)
 
+-- output arguments
+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 arguments 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
@@ -90,7 +110,8 @@ 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 parameters
+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 52318bf2a697d74c321b48a320bf590ccd9f3efa..429750d77cc63476d84768f5d1f9458d63625ffb 100644 (file)
@@ -46,6 +46,25 @@ CALL ptest3('b');
 SELECT * FROM cp_test;
 
 
+-- output arguments
+
+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