INOUT parameters in procedures
This patch set adds support for INOUT parameters to procedures.
Currently, INOUT and OUT parameters are not supported.
A top-level CALL returns the output parameters as a result row. In
PL/pgSQL, I have added special support to pass the output back into the
variables, as one would expect.
These patches apply on top of the "prokind" patch set v2. (Tom has
submitted an updated version of that, which overlaps with some of the
changes I've made here. I will work on consolidating that soon.)
So ... no OUT parameters, though. I'm struggling to find a way to make
this compatible with everything else. For functions, the OUT parameters
don't appear in the signature. But that is not how this is specified in
the SQL standard for procedures (I think). In PL/pgSQL, you'd expect that
CREATE PROCEDURE foo(a int, OUT b int) ...
could be called like
CALL foo(x, y);
but that would require a different way of parsing function invocation.
At the top-level, it's even more dubious. In DB2, apparently you write
CALL foo(123, ?);
with a literal ? for the OUT parameters.
In Oracle, I've seen CALL ... INTO syntax.
Anyway, I'm leaving this out for now. It can be worked around by using
INOUT parameters. Future improvements would be mainly syntax/parsing
adjustments; the guts that I'm implementing here would remain valid.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v1-0001-fixup-Add-prokind-column-replacing-proisagg-and-p.patchtext/plain; charset=UTF-8; name=v1-0001-fixup-Add-prokind-column-replacing-proisagg-and-p.patch; x-mac-creator=0; x-mac-type=0Download
From 127f3716a28cceca5077786e2cb3717e36dbb426 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 27 Feb 2018 09:55:32 -0500
Subject: [PATCH v1 1/2] fixup! Add prokind column, replacing proisagg and
proiswindow
---
src/backend/commands/dropcmds.c | 2 +-
src/backend/parser/parse_func.c | 6 +++---
src/backend/utils/cache/lsyscache.c | 12 ++++++------
src/include/utils/lsyscache.h | 2 +-
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index fc4ce8d22a..45493abf57 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -92,7 +92,7 @@ RemoveObjects(DropStmt *stmt)
*/
if (stmt->removeType == OBJECT_FUNCTION)
{
- if (get_func_isagg(address.objectId))
+ if (get_func_kind(address.objectId) == PROKIND_AGGREGATE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function",
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9dbf2c2b63..0b5145f70d 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2078,7 +2078,7 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool noError)
if (objtype == OBJECT_FUNCTION)
{
/* Make sure it's a function, not a procedure */
- if (oid && get_func_rettype(oid) == InvalidOid)
+ if (oid && get_func_kind(oid) == PROKIND_PROCEDURE)
{
if (noError)
return InvalidOid;
@@ -2109,7 +2109,7 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool noError)
}
/* Make sure it's a procedure */
- if (get_func_rettype(oid) != InvalidOid)
+ if (get_func_kind(oid) != PROKIND_PROCEDURE)
{
if (noError)
return InvalidOid;
@@ -2145,7 +2145,7 @@ LookupFuncWithArgs(ObjectType objtype, ObjectWithArgs *func, bool noError)
}
/* Make sure it's an aggregate */
- if (!get_func_isagg(oid))
+ if (get_func_kind(oid) != PROKIND_AGGREGATE)
{
if (noError)
return InvalidOid;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 161470aa34..869a937d5a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1600,20 +1600,20 @@ func_parallel(Oid funcid)
}
/*
- * get_func_isagg
- * Given procedure id, return whether the function is an aggregate.
+ * get_func_kind
+ * Given procedure id, return the function kind (prokind).
*/
-bool
-get_func_isagg(Oid funcid)
+char
+get_func_kind(Oid funcid)
{
HeapTuple tp;
- bool result;
+ char result;
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
- result = ((Form_pg_proc) GETSTRUCT(tp))->prokind == PROKIND_AGGREGATE;
+ result = ((Form_pg_proc) GETSTRUCT(tp))->prokind;
ReleaseSysCache(tp);
return result;
}
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 1f6c04a8f3..3014cabb35 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -117,7 +117,7 @@ extern bool get_func_retset(Oid funcid);
extern bool func_strict(Oid funcid);
extern char func_volatile(Oid funcid);
extern char func_parallel(Oid funcid);
-extern bool get_func_isagg(Oid funcid);
+extern char get_func_kind(Oid funcid);
extern bool get_func_leakproof(Oid funcid);
extern float4 get_func_cost(Oid funcid);
extern float4 get_func_rows(Oid funcid);
base-commit: bc1adc651b8e60680aea144d51ae8bc78ea6b2fb
prerequisite-patch-id: f46ead32b2ef64d66cc1994da7db6e57857c0270
prerequisite-patch-id: 62e1cab8d489dfd26a2222f5b0f48f17a48cb63f
--
2.16.2
v1-0002-Support-INOUT-parameters-in-procedures.patchtext/plain; charset=UTF-8; name=v1-0002-Support-INOUT-parameters-in-procedures.patch; x-mac-creator=0; x-mac-type=0Download
From 51725d637e343dde7438ea232f1cb1d16e8ad0a1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 28 Feb 2018 16:41:24 -0500
Subject: [PATCH v1 2/2] 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.
---
src/backend/commands/functioncmds.c | 44 ++++++-
src/backend/tcop/utility.c | 3 +-
src/include/commands/defrem.h | 3 +-
src/pl/plpgsql/src/expected/plpgsql_call.out | 60 +++++++++-
.../plpgsql/src/expected/plpgsql_transaction.out | 2 +-
src/pl/plpgsql/src/pl_comp.c | 1 +
src/pl/plpgsql/src/pl_exec.c | 131 ++++++++++++++++++++-
src/pl/plpgsql/src/pl_funcs.c | 25 ++++
src/pl/plpgsql/src/pl_gram.y | 35 +++++-
src/pl/plpgsql/src/pl_scanner.c | 2 +
src/pl/plpgsql/src/plpgsql.h | 13 ++
src/pl/plpgsql/src/sql/plpgsql_call.sql | 54 +++++++++
12 files changed, 358 insertions(+), 15 deletions(-)
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 29380f61c3..ac12af3a67 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,7 +282,7 @@ 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"))));
@@ -302,7 +303,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++;
}
@@ -1005,7 +1008,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
Assert(!stmt->returnType);
- prorettype = InvalidOid;
+ prorettype = requiredResultType;
returnsSet = false;
}
else if (stmt->returnType)
@@ -2203,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;
@@ -2216,6 +2219,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
EState *estate;
ExprContext *econtext;
HeapTuple tp;
+ Datum retval;
fexpr = stmt->funcexpr;
Assert(fexpr);
@@ -2282,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/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/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/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out
index e2442c603c..7d04136112 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -19,7 +19,7 @@ END;
$$;
CALL test_proc2();
ERROR: cannot return a value from a procedure
-CONTEXT: PL/pgSQL function test_proc2() while casting return value to function's return type
+CONTEXT: PL/pgSQL function test_proc2() line 3 at RETURN
CREATE TABLE test1 (a int);
CREATE PROCEDURE test_proc3(x int)
LANGUAGE plpgsql
@@ -53,6 +53,64 @@ SELECT * FROM test1;
66
(2 rows)
+-- OUT parameters
+CREATE PROCEDURE test_proc5(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ b := b * a;
+ c := c * a;
+END;
+$$;
+CALL test_proc5(2, 3, 4);
+ b | c
+---+---
+ 6 | 8
+(1 row)
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc5(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_proc5(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_proc5(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
DROP PROCEDURE test_proc1;
DROP PROCEDURE test_proc2;
DROP PROCEDURE test_proc3;
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 d07a16a7ea..7137e5dd37 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -351,6 +351,7 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_tid = procTup->t_self;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
+ function->fn_is_procedure = (get_func_kind(function->fn_oid) == PROKIND_PROCEDURE);
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
function->print_strict_params = plpgsql_print_strict_params;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index eae51e316a..f7630a96bb 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,
@@ -573,7 +577,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
estate.err_text = NULL;
estate.err_stmt = (PLpgSQL_stmt *) (func->action);
rc = exec_stmt_block(&estate, func->action);
- if (rc != PLPGSQL_RC_RETURN && func->fn_rettype)
+ if (rc != PLPGSQL_RC_RETURN && !func->fn_is_procedure)
{
estate.err_stmt = NULL;
estate.err_text = NULL;
@@ -617,10 +621,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
}
else if (!estate.retisnull)
{
- if (!func->fn_rettype)
- ereport(ERROR,
- (errmsg("cannot return a value from a procedure")));
-
/*
* Cast result value to function's declared result type, and copy it
* out to the upper executor memory context. We must treat tuple
@@ -1905,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;
@@ -2045,6 +2049,116 @@ 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 OUT/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;
+ 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
+ */
+ 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 || argmodes[i] == PROARGMODE_OUT))
+ {
+ 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;
+
+ 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.
@@ -2835,6 +2949,13 @@ exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
static int
exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
{
+ if (estate->func->fn_is_procedure)
+ {
+ if ((stmt->retvarno >= 0 && stmt->retvarno != estate->func->out_param_varno) || stmt->expr)
+ ereport(ERROR,
+ (errmsg("cannot return a value from a procedure")));
+ }
+
/*
* If processing a set-returning PL/pgSQL function, the final RETURN
* indicates that the function is finished producing tuples. The rest of
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 688fbd6531..9429cb6334 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -196,7 +196,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
@@ -256,6 +256,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
@@ -274,6 +275,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_DEFAULT
%token <keyword> K_DETAIL
%token <keyword> K_DIAGNOSTICS
+%token <keyword> K_DO
%token <keyword> K_DUMP
%token <keyword> K_ELSE
%token <keyword> K_ELSIF
@@ -871,6 +873,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_perform
{ $$ = $1; }
+ | stmt_call
+ { $$ = $1; }
| stmt_getdiag
{ $$ = $1; }
| stmt_open
@@ -902,6 +906,33 @@ 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;
+
+ }
+ | K_DO
+ {
+ /* use the same structures as for CALL, for simplicity */
+ 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("DO ");
+
+ $$ = (PLpgSQL_stmt *)new;
+
+ }
+ ;
+
stmt_assign : assign_var assign_operator expr_until_semi
{
PLpgSQL_stmt_assign *new;
@@ -2400,6 +2431,7 @@ unreserved_keyword :
| K_ARRAY
| K_ASSERT
| K_BACKWARD
+ | K_CALL
| K_CLOSE
| K_COLLATE
| K_COLUMN
@@ -2416,6 +2448,7 @@ unreserved_keyword :
| K_DEFAULT
| K_DETAIL
| K_DIAGNOSTICS
+ | K_DO
| K_DUMP
| K_ELSIF
| K_ERRCODE
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 12a3e6b818..08614a89a8 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)
@@ -118,6 +119,7 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("default", K_DEFAULT, UNRESERVED_KEYWORD)
PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("diagnostics", K_DIAGNOSTICS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("do", K_DO, UNRESERVED_KEYWORD)
PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
PG_KEYWORD("elseif", K_ELSIF, UNRESERVED_KEYWORD)
PG_KEYWORD("elsif", K_ELSIF, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 26a7344e9a..c920b8601e 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
*/
@@ -910,6 +922,7 @@ typedef struct PLpgSQL_function
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
+ bool fn_is_procedure;
Oid fn_rettype;
int fn_rettyplen;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql
index 321ed43af8..38763014ea 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -57,6 +57,60 @@ CREATE PROCEDURE test_proc4(y int)
SELECT * FROM test1;
+-- OUT parameters
+CREATE PROCEDURE test_proc5(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ b := b * a;
+ c := c * a;
+END;
+$$;
+
+CALL test_proc5(2, 3, 4);
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc5(2, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc5(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_proc5(i, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+ END LOOP;
+END;
+$$;
+
+
DROP PROCEDURE test_proc1;
DROP PROCEDURE test_proc2;
DROP PROCEDURE test_proc3;
--
2.16.2
Hi
2018-02-28 23:28 GMT+01:00 Peter Eisentraut <
peter.eisentraut@2ndquadrant.com>:
This patch set adds support for INOUT parameters to procedures.
Currently, INOUT and OUT parameters are not supported.A top-level CALL returns the output parameters as a result row. In
PL/pgSQL, I have added special support to pass the output back into the
variables, as one would expect.These patches apply on top of the "prokind" patch set v2. (Tom has
submitted an updated version of that, which overlaps with some of the
changes I've made here. I will work on consolidating that soon.)So ... no OUT parameters, though. I'm struggling to find a way to make
this compatible with everything else. For functions, the OUT parameters
don't appear in the signature. But that is not how this is specified in
the SQL standard for procedures (I think). In PL/pgSQL, you'd expect thatCREATE PROCEDURE foo(a int, OUT b int) ...
could be called like
CALL foo(x, y);
but that would require a different way of parsing function invocation.
At the top-level, it's even more dubious. In DB2, apparently you write
CALL foo(123, ?);
with a literal ? for the OUT parameters.
In Oracle, I've seen CALL ... INTO syntax.
Anyway, I'm leaving this out for now. It can be worked around by using
INOUT parameters. Future improvements would be mainly syntax/parsing
adjustments; the guts that I'm implementing here would remain valid.
I am looking on attached code, and it looks pretty well. Can be really nice
if this code will be part of release 11, because it is very interesting,
important feature feature.
Regards
p.s. can be nice, if we allow same trick with calling of OUT variables
functions in plpgsql
fx(in a, out x, out y) return int -- but requires some special mark
do $$
declare x int, y int, z int;
begin
z := fx(10, x, y);
raise notice '% ....
Then migration from Oracle can be really easy and friendly
Pavel
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
At the top-level, it's even more dubious. In DB2, apparently you write
CALL foo(123, ?);
with a literal ? for the OUT parameters.
That's not actually as scary as it seems.
DB2 has two cases where you can use a ? like that:
1) In CLP (DB2's equivalent to psql)
DB2 draws a distinct line between procedures and functions, and you have to
invoke procedures with CALL FOO(...). Since CLP doesn't support variables
(and SQL variables didn't exist in DB2 when the CALL statement was
introduced), they needed a way to say "there's an output parameter here" so
they settled on using ? as the placeholder. (? was chosen because it ties
nicely into the next point.)
2) In dynamic SQL
DB2 has traditionally used ? as a parameter marker (placeholder for a
variable) in dynamic SQL. So the usage would look something like:
DECLARE res INTEGER;
DECLARE text VARCHAR(50);
SET text = 'CALL foo(123, ?)';
PREPARE stmt FROM text;
EXECUTE stmt INTO res; -- This invokes the statement and maps the ? into
the variable "res"
If you didn't need/want to use dynamic SQL, then you could have simply
written:
CALL foo(123, res);
- Doug Doole
Salesforce
On 3/5/18 11:00, Pavel Stehule wrote:
I am looking on attached code, and it looks pretty well. Can be really
nice if this code will be part of release 11, because it is very
interesting, important feature feature.
Here is an updated patch, rebased on top of several recent changes, also
added more documentation and tests in other PLs.
p.s. can be nice, if we allow same trick with calling of OUT variables
functions in plpgsqlfx(in a, out x, out y) return int -- but requires some special mark
do $$
declare x int, y int, z int;
begin
z := fx(10, x, y);
raise notice '% ....Then migration from Oracle can be really easy and friendly
This would require some changes to how routines are looked up, because
we currently ignore OUT parameters there. That code does not exist yet.
But it's certainly a plausible extension for the future.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v2-0001-Support-INOUT-parameters-in-procedures.patchtext/plain; charset=UTF-8; name=v2-0001-Support-INOUT-parameters-in-procedures.patch; x-mac-creator=0; x-mac-type=0Download
From 8ce8ae9e59611e1a01f7507a6595f50416b761cc Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 5 Mar 2018 12:45:33 -0500
Subject: [PATCH v2] 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.
---
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 | 5 +-
src/backend/catalog/pg_proc.c | 3 +-
src/backend/commands/functioncmds.c | 48 +++++++--
src/backend/tcop/utility.c | 3 +-
src/backend/utils/fmgr/funcapi.c | 11 +-
src/include/commands/defrem.h | 3 +-
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 | 71 +++++++++++++
.../plpgsql/src/expected/plpgsql_transaction.out | 2 +-
src/pl/plpgsql/src/pl_comp.c | 10 +-
src/pl/plpgsql/src/pl_exec.c | 118 +++++++++++++++++++++
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 | 66 ++++++++++++
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 ++++
27 files changed, 588 insertions(+), 47 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 => $a * 3, b => $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..c28a729535 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -96,8 +96,9 @@ <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>.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 40e579f95d..3ffd75ef4d 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)
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index b1f87d056e..dc306da268 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,7 +282,7 @@ 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"))));
@@ -302,7 +303,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 +1006,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 +2205,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 +2218,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic)
EState *estate;
ExprContext *econtext;
HeapTuple tp;
+ Datum retval;
fexpr = stmt->funcexpr;
Assert(fexpr);
@@ -2285,7 +2285,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/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/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..6c1659e3be 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -53,6 +53,77 @@ 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
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..18e4dea694 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,116 @@ 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 OUT/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;
+ 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
+ */
+ 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 || argmodes[i] == PROARGMODE_OUT))
+ {
+ 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;
+
+ 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.
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..0bc2814f92 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -55,6 +55,72 @@ 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;
+$$;
+
+
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;
base-commit: 2f3e2340cd1b67e91cefdf45e4c915297d1034e2
--
2.16.2
2018-03-05 19:38 GMT+01:00 Peter Eisentraut <
peter.eisentraut@2ndquadrant.com>:
On 3/5/18 11:00, Pavel Stehule wrote:
I am looking on attached code, and it looks pretty well. Can be really
nice if this code will be part of release 11, because it is very
interesting, important feature feature.Here is an updated patch, rebased on top of several recent changes, also
added more documentation and tests in other PLs.p.s. can be nice, if we allow same trick with calling of OUT variables
functions in plpgsqlfx(in a, out x, out y) return int -- but requires some special mark
do $$
declare x int, y int, z int;
begin
z := fx(10, x, y);
raise notice '% ....Then migration from Oracle can be really easy and friendly
This would require some changes to how routines are looked up, because
we currently ignore OUT parameters there. That code does not exist yet.
But it's certainly a plausible extension for the future.
sure - this is topic for 12 release. But it can fix more than one issue
when PL/SQL code is migrated.
note: in this case we should to return one parameter more. Out parameters +
RETURN expression result.
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
2018-03-05 19:41 GMT+01:00 Pavel Stehule <pavel.stehule@gmail.com>:
2018-03-05 19:38 GMT+01:00 Peter Eisentraut <peter.eisentraut@2ndquadrant.
com>:On 3/5/18 11:00, Pavel Stehule wrote:
I am looking on attached code, and it looks pretty well. Can be really
nice if this code will be part of release 11, because it is very
interesting, important feature feature.Here is an updated patch, rebased on top of several recent changes, also
added more documentation and tests in other PLs.p.s. can be nice, if we allow same trick with calling of OUT variables
functions in plpgsqlfx(in a, out x, out y) return int -- but requires some special mark
do $$
declare x int, y int, z int;
begin
z := fx(10, x, y);
raise notice '% ....Then migration from Oracle can be really easy and friendly
This would require some changes to how routines are looked up, because
we currently ignore OUT parameters there. That code does not exist yet.
But it's certainly a plausible extension for the future.sure - this is topic for 12 release. But it can fix more than one issue
when PL/SQL code is migrated.note: in this case we should to return one parameter more. Out parameters
+ RETURN expression result.
this problem is simple/difficult. the type of function can be detected from
call context - when function is called with assigned out variable(s) (all
OUT variables should be assigned), then the behave should be classical -
and RETURN expression for non void functions should be required. Else, the
OUT variables should not be assigned, and function will be called in
postgresql style - the function returns tuple defined by OUT parameters and
RETURN expression is prohibited. Some hint can be returning type - if it is
not defined, then result is defined just by OUT variables, when it is
defined (and it is not RECORD), then RETURN expression is required. When
RETURNS type is RECORD, then we know nothing and decision should be based
on calling context.
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
2018-03-05 19:38 GMT+01:00 Peter Eisentraut <
peter.eisentraut@2ndquadrant.com>:
On 3/5/18 11:00, Pavel Stehule wrote:
I am looking on attached code, and it looks pretty well. Can be really
nice if this code will be part of release 11, because it is very
interesting, important feature feature.Here is an updated patch, rebased on top of several recent changes, also
added more documentation and tests in other PLs.
why just OUT variables are disallowed?
The oracle initializes these values to NULL - we can do same?
Minimally this message is not too friendly, there should be hint - "only
INOUT is suported" - but better support OUT too - from TOP OUT variables
should not be passed. from PL should be required.
I wrote recursive procedure. The call finished by exception. Why?
create or replace procedure p(x int,inout a int, inout b numeric)
as $$
begin
raise notice 'xxx % %', a, b;
if (x > 1) then
a := x / 10;
b := x / 2;
call p(b::int, a, b);
end if;
end;
$$ language plpgsql;
CREATE PROCEDURE
postgres=# call p(100, -1, -1);
NOTICE: xxx -1 -1
NOTICE: xxx 10 50
NOTICE: xxx 5 25
NOTICE: xxx 2 12
NOTICE: xxx 1 6
NOTICE: xxx 0 3
NOTICE: xxx 0 1
ERROR: unsupported target
CONTEXT: PL/pgSQL function p(integer,integer,numeric) line 4 at CALL
SQL statement "CALL p(b::int, a, b)"
PL/pgSQL function p(integer,integer,numeric) line 4 at CALL
SQL statement "CALL p(b::int, a, b)"
PL/pgSQL function p(integer,integer,numeric) line 4 at CALL
SQL statement "CALL p(b::int, a, b)"
PL/pgSQL function p(integer,integer,numeric) line 4 at CALL
SQL statement "CALL p(b::int, a, b)"
PL/pgSQL function p(integer,integer,numeric) line 4 at CALL
Because these variables are INOUT then it should work.
This issue can be detected in compile time, maybe?
postgres=# create or replace procedure p(x int,inout a int, inout b numeric)
as $$
begin raise notice 'xxx % %', a, b;if (x > 1) then
a := x / 10;
b := x / 2; call p(b::int, a, 10); <--- can be detected in compile time?
end if;
end;
$$ language plpgsql;
Is terrible, how this patch is short.
Regards
Pavel
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 3/6/18 04:22, Pavel Stehule wrote:
why just OUT variables are disallowed?
The oracle initializes these values to NULL - we can do same?
The problem is function call resolution. If we see a call like
CALL foo(a, b, c);
the this could be foo() with zero input and three output parameters, or
with one input parameter and two output parameters, etc. We have no
code to deal with that right now.
Minimally this message is not too friendly, there should be hint - "only
INOUT is suported" - but better support OUT too - from TOP OUT variables
should not be passed. from PL should be required.
Added a hint.
I wrote recursive procedure. The call finished by exception. Why?
Fixed. (memory context issue)
I added your example as a test case.
This issue can be detected in compile time, maybe?
postgres=# create or replace procedure p(x int,inout a int, inout b numeric)
as $$
begin raise notice 'xxx % %', a, b;if (x > 1) then
a := x / 10;
b := x / 2; call p(b::int, a, 10); <--- can be detected in compile time?
end if;
end;
$$ language plpgsql;
Function resolution doesn't happen at compile time. That would require
significant work in PL/pgSQL (possible perhaps, but major work). Right
now, we do parse analysis at first execution.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v3-0001-Support-INOUT-parameters-in-procedures.patchtext/plain; charset=UTF-8; name=v3-0001-Support-INOUT-parameters-in-procedures.patch; x-mac-creator=0; x-mac-type=0Download
From 3c5ed2faab30dfcde34dfd58877e45a7f6477237 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 7 Mar 2018 19:15:35 -0500
Subject: [PATCH v3] 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.
---
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 | 5 +-
src/backend/catalog/pg_proc.c | 3 +-
src/backend/commands/functioncmds.c | 51 +++++++--
src/backend/tcop/utility.c | 3 +-
src/backend/utils/fmgr/funcapi.c | 11 +-
src/include/commands/defrem.h | 3 +-
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 | 83 ++++++++++++++
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 | 1 +
28 files changed, 632 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 => $a * 3, b => $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..c28a729535 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -96,8 +96,9 @@ <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>.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 40e579f95d..3ffd75ef4d 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)
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/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/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..42ac2bf6f6 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 OUT/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 || argmodes[i] == PROARGMODE_OUT))
+ {
+ 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..e8b2e0d2f3 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -55,6 +55,89 @@ 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);
+
+
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..72c9b4f0e2 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -91,6 +91,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;
base-commit: f9f8784c8b3050aaec52da88a6e41b3e3f576b96
--
2.16.2
Hi
2018-03-08 1:53 GMT+01:00 Peter Eisentraut <peter.eisentraut@2ndquadrant.com
:
On 3/6/18 04:22, Pavel Stehule wrote:
why just OUT variables are disallowed?
The oracle initializes these values to NULL - we can do same?
The problem is function call resolution. If we see a call like
CALL foo(a, b, c);
the this could be foo() with zero input and three output parameters, or
with one input parameter and two output parameters, etc. We have no
code to deal with that right now.
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.
CREATE OR REPLACE PROCEDURE proc(IN a, OUT x, OUT y)
AS $$
BEGIN
x := a * 10;
y := a + 10;
END;
$$ LANGUAGE plpgsql;
CALL proc(10) -- has sense
but because just OUT variables are not possible, then the definition must
be changed to CREATE OR REPLACE PROCEDURE proc(IN a, INOUT x, INOUT y)
and CALL proc(10, NULL, NULL) -- looks little bit scarry
I understand so this is not easy solution (and it can be topic for other
releases), but I am thinking so it is solvable - but needs deeper change in
part, where is a routine is selected on signature. Now, this algorithm
doesn't calculate with OUT params.
This enhancing can be interesting for some purposes (and again it can helps
with migration from Oracle - although these techniques are usually used
inside system libraries):
a) taking more info from proc when it is required
PROCEDURE foo(a int);
PROCEDURE foo(a int, OUT detail text)
b) possible to directly specify expected result type
PROCEDURE from_json(a json, OUT int);
PROCEDURE from_json(a json, OUT date);
PROCEDURE from_json(a json, OUT text);
It is clear, so in environments when variables are not available, these
procedures cannot be called doe possible ambiguity.
This point can be closed now, I accept technical limits.
Minimally this message is not too friendly, there should be hint - "only
INOUT is suported" - but better support OUT too - from TOP OUT variables
should not be passed. from PL should be required.Added a hint.
ok
I wrote recursive procedure. The call finished by exception. Why?
Fixed. (memory context issue)
tested, it is ok now
I added your example as a test case.
This issue can be detected in compile time, maybe?
postgres=# create or replace procedure p(x int,inout a int, inout b
numeric)
as $$
begin raise notice 'xxx % %', a, b;if (x > 1) then
a := x / 10;
b := x / 2; call p(b::int, a, 10); <--- can be detected in compiletime?
end if;
end;
$$ language plpgsql;Function resolution doesn't happen at compile time. That would require
significant work in PL/pgSQL (possible perhaps, but major work). Right
now, we do parse analysis at first execution.
ok, understand
looks well
all test passed,
code is well commented,
there are tests
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.
Regards
Pavel
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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
Attachments:
v4-0001-Support-INOUT-parameters-in-procedures.patchtext/plain; charset=UTF-8; name=v4-0001-Support-INOUT-parameters-in-procedures.patch; x-mac-creator=0; x-mac-type=0Download
From 5b9f1506e73826f4f6ff567e54b12c4e232a4263 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@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.stehule@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 => $a * 3, b => $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
2018-03-13 14:14 GMT+01:00 Peter Eisentraut <
peter.eisentraut@2ndquadrant.com>:
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.
yes
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?
The disabling it, it is probably the best what is possible now. The
variables in SQL are more named parameters than variables. Is not necessary
to complicate it.
Regards
Pavel
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
committed
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Mar 14, 2018 at 10:46 AM, Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:
committed
I'm getting compiler warnings:
pl_exec.c: In function 'exec_stmt_call':
pl_exec.c:2089:8: warning: variable 'numargs' set but not used
[-Wunused-but-set-variable]
int numargs;
^
select version();
PostgreSQL 11devel-6b960aa on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu
5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609, 64-bit
Cheers,
Jeff
Jeff Janes <jeff.janes@gmail.com> writes:
I'm getting compiler warnings:
pl_exec.c: In function 'exec_stmt_call':
pl_exec.c:2089:8: warning: variable 'numargs' set but not used
Not fixed by 8df5a1c868cc28f89ac6221cff8e2b5c952d0eb6?
regards, tom lane
On Thu, Mar 15, 2018 at 6:58 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Jeff Janes <jeff.janes@gmail.com> writes:
I'm getting compiler warnings:
pl_exec.c: In function 'exec_stmt_call':
pl_exec.c:2089:8: warning: variable 'numargs' set but not usedNot fixed by 8df5a1c868cc28f89ac6221cff8e2b5c952d0eb6?
I think you meant to type "now fixed by". (unless your compiler is pickier
than mine)
Cheers
Jeff
Jeff Janes <jeff.janes@gmail.com> writes:
On Thu, Mar 15, 2018 at 6:58 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Not fixed by 8df5a1c868cc28f89ac6221cff8e2b5c952d0eb6?
I think you meant to type "now fixed by". (unless your compiler is pickier
than mine)
Actually what I meant was "doesn't that commit fix it for you?"
regards, tom lane
Thanks Peter for working on this. Sorry for the delay in raising this
questions.
1)
@@ -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++;
For the FUNCTION when we have single OUT/INOUT parameter
the return type for that function will be set to the type of OUT parameter.
But in case of PROCEDURE, it's always RECORDOID, why this inconsistency?
postgres@39755=#select proname, prorettype from pg_proc where proname =
'foo';
proname | prorettype
---------+------------
foo | 23
(1 row)
postgres@39755=#CREATE PROCEDURE foo_pro(INOUT a int)
LANGUAGE plpgsql
AS $$
begin
SELECT 1 into a;
end;$$;
CREATE PROCEDURE
postgres@39755=#select proname, prorettype from pg_proc where proname =
'foo_pro';
proname | prorettype
---------+------------
foo_pro | 2249
(1 row)
2) Inconsistency in procedure behavior - compared to function.
drop procedure ptest4a;
drop procedure ptest4b;
CREATE PROCEDURE ptest4a(INOUT a int, INOUT b int)
LANGUAGE plpgsql
AS $$
begin
SELECT 1, 2 into a, b;
end;$$;
CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
LANGUAGE SQL
AS $$
CALL ptest4a(a, b);
$$;
ERROR: calling procedures with output arguments is not supported in SQL
functions
CONTEXT: SQL function "ptest4b"
Above test throws an error saying calling procedures with output
arguments are not supported in SQL functions. Whereas similar test
do work with SQL functions:
CREATE FUNCTION ftest4a(INOUT a int, INOUT b int) returns record
LANGUAGE plpgsql
AS $$
begin
SELECT 1, 2 into a, b;
end;$$;
CREATE FUNCTION ftest4b(INOUT b int, INOUT a int) returns record
LANGUAGE SQL
AS $$
SELECT ftest4a(a, b);
$$;
postgres@39755=#SELECT ftest4b(null, null);
ftest4b
---------
(1,2)
(1 row)
3)
CREATE PROCEDURE ptest4b(INOUT b int, INOUT a int)
LANGUAGE SQL
AS $$
CALL ptest4a(a, b);
$$;
ERROR: calling procedures with output arguments is not supported in SQL
functions
CONTEXT: SQL function "ptest4b"
Here error message says that calling procedures with output arguments is not
supported in SQL functions. Whereas here it's getting called from the SQL
procedure. So error message needs to be changed.
Thanks,
Rushabh Lathia
www.EnterpriseDB.com
On 3/19/18 03:25, Rushabh Lathia wrote:
For the FUNCTION when we have single OUT/INOUT parameter
the return type for that function will be set to the type of OUT parameter.
But in case of PROCEDURE, it's always RECORDOID, why this inconsistency?
For procedures, this is just an implementation detail. The CALL command
returns a row in any case, so if we set the return type to a scalar
type, we'd have to add special code to reassemble a row anyway. For
functions, the inconsistency is (arguably) worth it, because it affects
how functions can be written and called, but for procedures, there would
be no point.
Above test throws an error saying calling procedures with output
arguments are not supported in SQL functions. Whereas similar test
do work with SQL functions:
This was discussed earlier in the thread.
The behavior of output parameters in functions was, AFAICT, invented by
us. But for procedures, the SQL standard specifies it, so there might
be some differences.
ERROR: calling procedures with output arguments is not supported in SQL
functions
CONTEXT: SQL function "ptest4b"Here error message says that calling procedures with output arguments is not
supported in SQL functions. Whereas here it's getting called from the SQL
procedure. So error message needs to be changed.
Well, I don't think we are going to change every single error message
from "function" to a separate function and procedure variant.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Mar 20, 2018 at 6:38 AM, Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:
On 3/19/18 03:25, Rushabh Lathia wrote:
For the FUNCTION when we have single OUT/INOUT parameter
the return type for that function will be set to the type of OUTparameter.
But in case of PROCEDURE, it's always RECORDOID, why this inconsistency?
For procedures, this is just an implementation detail. The CALL command
returns a row in any case, so if we set the return type to a scalar
type, we'd have to add special code to reassemble a row anyway. For
functions, the inconsistency is (arguably) worth it, because it affects
how functions can be written and called, but for procedures, there would
be no point.
This feel like inconsistency with the existing system object FUNCTION.
It would be nice to be consistent with the FUNCTION - which set the
prorettype as the type of single IN/OUT in case of single argument.
If CALL command returns a row in any case, then I think adding logic
to build row while building the output for CALL statement make more sense.
Above test throws an error saying calling procedures with output
arguments are not supported in SQL functions. Whereas similar test
do work with SQL functions:This was discussed earlier in the thread.
The behavior of output parameters in functions was, AFAICT, invented by
us. But for procedures, the SQL standard specifies it, so there might
be some differences.
Sorry, but I am still unable to understand the difference.
In case of PROCEDURE, it's calling the PROCEDURE with out parameter.
So if that we call the same PROCEURE in the psql prompt:
postgres@101361=#CALL ptest4a(null, null);
a | b
---+---
1 | 2
(1 row)
and same is the case if we call the FUNCTION in the psql prompt:
postgres@101361=#SELECT * from ftest4b(null, null);
b | a
---+---
1 | 2
(1 row)
So if I understand correctly, in the testcase where it's calling the CALL
within SQL procedure - has to throw similar output. Isn't it?
ERROR: calling procedures with output arguments is not supported in SQL
functions
CONTEXT: SQL function "ptest4b"Here error message says that calling procedures with output arguments is
not
supported in SQL functions. Whereas here it's getting called from the
SQL
procedure. So error message needs to be changed.
Well, I don't think we are going to change every single error message
from "function" to a separate function and procedure variant.
I think we should, otherwise it pass the wrong message to the user. Like
here it says "calling procedures with output arguments is not supported in
SQL functions"
but actually test is calling the procedures from procedure. I think now
that
we have a way to ideintify FUNCTION/PROCEDURE (prokind) it's good
to give proper error message.
Recently commit 2c6f37ed62114bd5a092c20fe721bd11b3bcb91e and
8b9e9644dc6a9bd4b7a97950e6212f63880cf18b replace AclObjectKind and
GrantObjectType with ObjectType and with that we now getting proper
object type for the acl error message. In case of PROCEDURE
and FUNCTIONS also error message should send clear message.
Regards,
Rushabh Lathia
www.EnterpriseDB.com
On Wed, Feb 28, 2018 at 4:28 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
This patch set adds support for INOUT parameters to procedures.
Currently, INOUT and OUT parameters are not supported.A top-level CALL returns the output parameters as a result row. In
PL/pgSQL, I have added special support to pass the output back into the
variables, as one would expect.These patches apply on top of the "prokind" patch set v2. (Tom has
submitted an updated version of that, which overlaps with some of the
changes I've made here. I will work on consolidating that soon.)
I did a pull from master to play around with INOUT parameters and got
some strange interactions with DEFAULT. Specifically, DEFAULT doesn't
do much beyond, 'return the last supplied value given'. I'm not sure
if this is expected behavior; it seems odd:
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
postgres=# call p();
a
───
(1 row)
postgres=# call p(3);
a
───
3
(1 row)
postgres=# call p();
a
───
3
(1 row)
I got null,3,3. I would have expected 7,3,7. Default arguments might
remove quite some of the pain associated with having to supply bogus
arguments to get the INOUT parameters working.
Edit: In one case, after dropping the function and recreating it, I
got the procedure to return 0 where it had not before, so this smells
like a bug.
postgres=# call p();
2018-03-20 09:04:50.543 CDT [21494] ERROR: function p() does not
exist at character 6
2018-03-20 09:04:50.543 CDT [21494] HINT: No function matches the
given name and argument types. You might need to add explicit type
casts.
2018-03-20 09:04:50.543 CDT [21494] STATEMENT: call p();
ERROR: function p() does not exist
LINE 1: call p();
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.
Time: 0.297 ms
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
Time: 1.182 ms
postgres=# call p();
a
───
0
(1 row)
merlin
2018-03-20 15:05 GMT+01:00 Merlin Moncure <mmoncure@gmail.com>:
On Wed, Feb 28, 2018 at 4:28 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:This patch set adds support for INOUT parameters to procedures.
Currently, INOUT and OUT parameters are not supported.A top-level CALL returns the output parameters as a result row. In
PL/pgSQL, I have added special support to pass the output back into the
variables, as one would expect.These patches apply on top of the "prokind" patch set v2. (Tom has
submitted an updated version of that, which overlaps with some of the
changes I've made here. I will work on consolidating that soon.)I did a pull from master to play around with INOUT parameters and got
some strange interactions with DEFAULT. Specifically, DEFAULT doesn't
do much beyond, 'return the last supplied value given'. I'm not sure
if this is expected behavior; it seems odd:postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
postgres=# call p();
a
───(1 row)
postgres=# call p(3);
a
───
3
(1 row)postgres=# call p();
a
───
3
(1 row)I got null,3,3. I would have expected 7,3,7. Default arguments might
remove quite some of the pain associated with having to supply bogus
arguments to get the INOUT parameters working.Edit: In one case, after dropping the function and recreating it, I
got the procedure to return 0 where it had not before, so this smells
like a bug.
postgres=# call p();
2018-03-20 09:04:50.543 CDT [21494] ERROR: function p() does not
exist at character 6
2018-03-20 09:04:50.543 CDT [21494] HINT: No function matches the
given name and argument types. You might need to add explicit type
casts.
2018-03-20 09:04:50.543 CDT [21494] STATEMENT: call p();
ERROR: function p() does not exist
LINE 1: call p();
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.
Time: 0.297 ms
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
Time: 1.182 ms
postgres=# call p();
a
───
0
(1 row)
I wrote patch
Regards
Pavel
Show quoted text
merlin
Attachments:
plpgsql-call-named-default-args.patchtext/x-patch; charset=US-ASCII; name=plpgsql-call-named-default-args.patchDownload
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 86fa8c0dd7..c7a44d858b 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -55,6 +55,7 @@
#include "executor/executor.h"
#include "miscadmin.h"
#include "optimizer/var.h"
+#include "optimizer/clauses.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -2254,6 +2255,9 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
if (!heap_attisnull(tp, Anum_pg_proc_proconfig))
callcontext->atomic = true;
+
+ fexpr->args = expand_function_arguments(fexpr->args, fexpr->funcresulttype, tp);
+
ReleaseSysCache(tp);
/* Initialize function call structure */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a9a09afd2b..40eae3a835 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -130,8 +130,6 @@ static Expr *simplify_function(Oid funcid,
Oid result_collid, Oid input_collid, List **args_p,
bool funcvariadic, bool process_args, bool allow_non_const,
eval_const_expressions_context *context);
-static List *expand_function_arguments(List *args, Oid result_type,
- HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
static List *add_function_defaults(List *args, HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
@@ -4112,7 +4110,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
* cases it handles should never occur there. This should be OK since it
* will fall through very quickly if there's nothing to do.
*/
-static List *
+List *
expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index ba4fa4b68b..ed854fdd40 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -14,9 +14,9 @@
#ifndef CLAUSES_H
#define CLAUSES_H
+#include "access/htup.h"
#include "nodes/relation.h"
-
#define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr))
#define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr))
@@ -85,4 +85,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
+extern List *expand_function_arguments(List *args, Oid result_type,
+ HeapTuple func_tuple);
+
#endif /* CLAUSES_H */
diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out
index 1e94a44f2b..9d7d7da5b0 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -169,3 +169,80 @@ DROP PROCEDURE test_proc1;
DROP PROCEDURE test_proc3;
DROP PROCEDURE test_proc4;
DROP TABLE test1;
+-- named parameters and defaults
+CREATE PROCEDURE test_proc(a int, b int, c int DEFAULT -1)
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+END;
+$$ LANGUAGE plpgsql;
+CALL test_proc(10,20,30);
+NOTICE: a: 10, b: 20, c: 30
+CALL test_proc(10,20);
+NOTICE: a: 10, b: 20, c: -1
+CALL test_proc(c=>1, a=>3, b=>2);
+NOTICE: a: 3, b: 2, c: 1
+DROP PROCEDURE test_proc;
+CREATE PROCEDURE test_proc1(INOUT _a int, INOUT _b int)
+AS $$
+BEGIN
+ RAISE NOTICE 'test_proc1: a: %, b: %', _a, _b;
+ _a := _a * 10;
+ _b := _b + 10;
+END;
+$$ LANGUAGE plpgsql;
+CALL test_proc1(10,20);
+NOTICE: test_proc1: a: 10, b: 20
+ _a | _b
+-----+----
+ 100 | 30
+(1 row)
+
+CALL test_proc1(_b=>20, _a=>10);
+NOTICE: test_proc1: a: 10, b: 20
+ _a | _b
+-----+----
+ 100 | 30
+(1 row)
+
+DO $$
+DECLARE a int; b int;
+BEGIN
+ a := 10; b := 30;
+ CALL test_proc1(a, b);
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := 10; b := 30;
+ CALL test_proc1(_b=>b, _a=>a);
+ RAISE NOTICE 'a: %, b: %', a, b;
+END
+$$;
+NOTICE: test_proc1: a: 10, b: 30
+NOTICE: a: 100, b: 40
+NOTICE: test_proc1: a: 10, b: 30
+NOTICE: a: 100, b: 40
+DROP PROCEDURE test_proc1;
+CREATE PROCEDURE test_proc1(INOUT _a int, INOUT _b int, INOUT _c int)
+AS $$
+BEGIN
+ RAISE NOTICE 'test_proc1: a: %, b: %, c: %', _a, _b, _c;
+ _a := _a * 10;
+ _b := _b + 10;
+ _c := _c * -10;
+END;
+$$ LANGUAGE plpgsql;
+DO $$
+DECLARE a int; b int; c int;
+BEGIN
+ a := 10; b := 30; c := 50;
+ CALL test_proc1(a, b, c);
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := 10; b := 30; c := 50;
+ CALL test_proc1(a, _c=>c, _b=>b);
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+END
+$$;
+NOTICE: test_proc1: a: 10, b: 30, c: 50
+NOTICE: a: 100, b: 40, c: -500
+NOTICE: test_proc1: a: 10, b: 30, c: 50
+NOTICE: a: 100, b: 40, c: -500
+DROP PROCEDURE test_proc1;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 68da7cf669..e073b15d60 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -2134,14 +2134,36 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
{
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)));
+ if (IsA(n,NamedArgExpr))
+ {
+ NamedArgExpr *nexpr = (NamedArgExpr *) n;
+
+ if (!IsA(nexpr->arg, 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;
+ param = castNode(Param, nexpr->arg);
+
+ /* paramid is offset by 1 (see make_datum_param()) */
+ /* should be ensured so this target varno is not used yet */
+ row->varnos[nexpr->argnumber] = param->paramid - 1;
+
+ /* named arguments must be after possition arguments, so I can increase nfields */
+ nfields++;
+ }
+ else
+ {
+ 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++;
}
diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql
index f1eed9975a..3f21dd9ea6 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -167,3 +167,67 @@ DROP PROCEDURE test_proc3;
DROP PROCEDURE test_proc4;
DROP TABLE test1;
+
+-- named parameters and defaults
+CREATE PROCEDURE test_proc(a int, b int, c int DEFAULT -1)
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+END;
+$$ LANGUAGE plpgsql;
+
+CALL test_proc(10,20,30);
+CALL test_proc(10,20);
+CALL test_proc(c=>1, a=>3, b=>2);
+
+DROP PROCEDURE test_proc;
+
+CREATE PROCEDURE test_proc1(INOUT _a int, INOUT _b int)
+AS $$
+BEGIN
+ RAISE NOTICE 'test_proc1: a: %, b: %', _a, _b;
+ _a := _a * 10;
+ _b := _b + 10;
+END;
+$$ LANGUAGE plpgsql;
+
+CALL test_proc1(10,20);
+CALL test_proc1(_b=>20, _a=>10);
+
+DO $$
+DECLARE a int; b int;
+BEGIN
+ a := 10; b := 30;
+ CALL test_proc1(a, b);
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := 10; b := 30;
+ CALL test_proc1(_b=>b, _a=>a);
+ RAISE NOTICE 'a: %, b: %', a, b;
+END
+$$;
+
+DROP PROCEDURE test_proc1;
+
+CREATE PROCEDURE test_proc1(INOUT _a int, INOUT _b int, INOUT _c int)
+AS $$
+BEGIN
+ RAISE NOTICE 'test_proc1: a: %, b: %, c: %', _a, _b, _c;
+ _a := _a * 10;
+ _b := _b + 10;
+ _c := _c * -10;
+END;
+$$ LANGUAGE plpgsql;
+
+DO $$
+DECLARE a int; b int; c int;
+BEGIN
+ a := 10; b := 30; c := 50;
+ CALL test_proc1(a, b, c);
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := 10; b := 30; c := 50;
+ CALL test_proc1(a, _c=>c, _b=>b);
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+END
+$$;
+
+DROP PROCEDURE test_proc1;
On Tue, Mar 20, 2018 at 9:09 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Edit: In one case, after dropping the function and recreating it, I
got the procedure to return 0 where it had not before, so this smells
like a bug.
postgres=# call p();
2018-03-20 09:04:50.543 CDT [21494] ERROR: function p() does not
exist at character 6
2018-03-20 09:04:50.543 CDT [21494] HINT: No function matches the
given name and argument types. You might need to add explicit type
casts.
2018-03-20 09:04:50.543 CDT [21494] STATEMENT: call p();
ERROR: function p() does not exist
LINE 1: call p();
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.
Time: 0.297 ms
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
Time: 1.182 ms
postgres=# call p();
a
───
0
(1 row)I wrote patch
Confirmed this fixes the issue.
merlin
2018-03-20 15:18 GMT+01:00 Merlin Moncure <mmoncure@gmail.com>:
On Tue, Mar 20, 2018 at 9:09 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:Edit: In one case, after dropping the function and recreating it, I
got the procedure to return 0 where it had not before, so this smells
like a bug.
postgres=# call p();
2018-03-20 09:04:50.543 CDT [21494] ERROR: function p() does not
exist at character 6
2018-03-20 09:04:50.543 CDT [21494] HINT: No function matches the
given name and argument types. You might need to add explicit type
casts.
2018-03-20 09:04:50.543 CDT [21494] STATEMENT: call p();
ERROR: function p() does not exist
LINE 1: call p();
^
HINT: No function matches the given name and argument types. You
might need to add explicit type casts.
Time: 0.297 ms
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
Time: 1.182 ms
postgres=# call p();
a
───
0
(1 row)I wrote patch
Confirmed this fixes the issue.
Thanks for info
Pavel
Show quoted text
merlin
On Tue, Mar 20, 2018 at 10:09 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
2018-03-20 15:18 GMT+01:00 Merlin Moncure <mmoncure@gmail.com>:
postgres=# create or replace procedure p(a inout int default 7) as $$
begin return; end; $$ language plpgsql;
CREATE PROCEDURE
Time: 1.182 ms
postgres=# call p();
a
───
0
(1 row)I wrote patch
Confirmed this fixes the issue.
Thanks for info
You're welcome. Working with this feature some more, I noticed that:
A) you can't assign output variables with into:
CALL p(1) INTO i; // gives syntax error
B) you can't assign via assignment
i := p(1); // gives error, 'use CALL'
C) but you *can* via execute
EXECUTE 'CALL p(1)' INTO i; // this works!
...I'm glad 'C' works, as without that there would be no useful way to
get values out of procedures called from within other
procedures/functions as things stand today. 'A' ideally also out to
work, but I'm not sure 'B' should be expected to work since it's
really a thin layer around SELECT. What do you think?
merlin
On Tue, Mar 20, 2018 at 4:19 PM, Merlin Moncure <mmoncure@gmail.com> wrote:
A) you can't assign output variables with into:
CALL p(1) INTO i; // gives syntax errorB) you can't assign via assignment
i := p(1); // gives error, 'use CALL'C) but you *can* via execute
EXECUTE 'CALL p(1)' INTO i; // this works!...I'm glad 'C' works, as without that there would be no useful way to
get values out of procedures called from within other
procedures/functions as things stand today. 'A' ideally also out to
work, but I'm not sure 'B' should be expected to work since it's
really a thin layer around SELECT. What do you think?
Also (sorry for spam),
A procedure created via:
create procedure p() as $$begin call p(); end; $$ language plpgsql;
...will segfault when called -- there ought to be a stack depth check.
merlin