Commit a8677e3f authored by Peter Eisentraut's avatar Peter Eisentraut

Support named and default arguments in CALL

We need to call expand_function_arguments() to expand named and default
arguments.

In PL/pgSQL, we also need to deal with named and default INOUT arguments
when receiving the output values into variables.

Author: Pavel Stehule <pavel.stehule@gmail.com>
parent 7c44c46d
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
#include "executor/execdesc.h" #include "executor/execdesc.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h" #include "optimizer/var.h"
#include "parser/parse_coerce.h" #include "parser/parse_coerce.h"
#include "parser/parse_collate.h" #include "parser/parse_collate.h"
...@@ -2226,34 +2227,40 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver ...@@ -2226,34 +2227,40 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
if (aclresult != ACLCHECK_OK) if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid)); aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid));
nargs = list_length(fexpr->args);
/* safety check; see ExecInitFunc() */
if (nargs > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg_plural("cannot pass more than %d argument to a procedure",
"cannot pass more than %d arguments to a procedure",
FUNC_MAX_ARGS,
FUNC_MAX_ARGS)));
/* Prep the context object we'll pass to the procedure */ /* Prep the context object we'll pass to the procedure */
callcontext = makeNode(CallContext); callcontext = makeNode(CallContext);
callcontext->atomic = atomic; callcontext->atomic = atomic;
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
/* /*
* If proconfig is set we can't allow transaction commands because of the * If proconfig is set we can't allow transaction commands because of the
* way the GUC stacking works: The transaction boundary would have to pop * way the GUC stacking works: The transaction boundary would have to pop
* the proconfig setting off the stack. That restriction could be lifted * the proconfig setting off the stack. That restriction could be lifted
* by redesigning the GUC nesting mechanism a bit. * by redesigning the GUC nesting mechanism a bit.
*/ */
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL)) if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL))
callcontext->atomic = true; callcontext->atomic = true;
/*
* Expand named arguments, defaults, etc.
*/
fexpr->args = expand_function_arguments(fexpr->args, fexpr->funcresulttype, tp);
nargs = list_length(fexpr->args);
ReleaseSysCache(tp); ReleaseSysCache(tp);
/* safety check; see ExecInitFunc() */
if (nargs > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg_plural("cannot pass more than %d argument to a procedure",
"cannot pass more than %d arguments to a procedure",
FUNC_MAX_ARGS,
FUNC_MAX_ARGS)));
/* Initialize function call structure */ /* Initialize function call structure */
InvokeFunctionExecuteHook(fexpr->funcid); InvokeFunctionExecuteHook(fexpr->funcid);
fmgr_info(fexpr->funcid, &flinfo); fmgr_info(fexpr->funcid, &flinfo);
......
...@@ -130,8 +130,6 @@ static Expr *simplify_function(Oid funcid, ...@@ -130,8 +130,6 @@ static Expr *simplify_function(Oid funcid,
Oid result_collid, Oid input_collid, List **args_p, Oid result_collid, Oid input_collid, List **args_p,
bool funcvariadic, bool process_args, bool allow_non_const, bool funcvariadic, bool process_args, bool allow_non_const,
eval_const_expressions_context *context); 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 *reorder_function_arguments(List *args, HeapTuple func_tuple);
static List *add_function_defaults(List *args, HeapTuple func_tuple); static List *add_function_defaults(List *args, HeapTuple func_tuple);
static List *fetch_function_defaults(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, ...@@ -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 * cases it handles should never occur there. This should be OK since it
* will fall through very quickly if there's nothing to do. * 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) expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
{ {
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
......
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
#ifndef CLAUSES_H #ifndef CLAUSES_H
#define CLAUSES_H #define CLAUSES_H
#include "access/htup.h"
#include "nodes/relation.h" #include "nodes/relation.h"
#define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr)) #define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr))
#define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr)) #define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr))
...@@ -85,4 +85,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node); ...@@ -85,4 +85,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
extern Query *inline_set_returning_function(PlannerInfo *root, extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte); RangeTblEntry *rte);
extern List *expand_function_arguments(List *args, Oid result_type,
HeapTuple func_tuple);
#endif /* CLAUSES_H */ #endif /* CLAUSES_H */
...@@ -152,6 +152,93 @@ CALL test_proc7(100, -1, -1); ...@@ -152,6 +152,93 @@ CALL test_proc7(100, -1, -1);
0 | 1 0 | 1
(1 row) (1 row)
-- named parameters and defaults
CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %', a, b;
a := a * 10;
b := b + 10;
END;
$$;
CALL test_proc8a(10, 20);
NOTICE: a: 10, b: 20
a | b
-----+----
100 | 30
(1 row)
CALL test_proc8a(b => 20, a => 10);
NOTICE: a: 10, b: 20
a | b
-----+----
100 | 30
(1 row)
DO $$
DECLARE _a int; _b int;
BEGIN
_a := 10; _b := 30;
CALL test_proc8a(_a, _b);
RAISE NOTICE '_a: %, _b: %', _a, _b;
CALL test_proc8a(b => _b, a => _a);
RAISE NOTICE '_a: %, _b: %', _a, _b;
END
$$;
NOTICE: a: 10, b: 30
NOTICE: _a: 100, _b: 40
NOTICE: a: 100, b: 40
NOTICE: _a: 1000, _b: 50
CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
a := a * 10;
b := b + 10;
c := c * -10;
END;
$$;
DO $$
DECLARE _a int; _b int; _c int;
BEGIN
_a := 10; _b := 30; _c := 50;
CALL test_proc8b(_a, _b, _c);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
CALL test_proc8b(_a, c => _c, b => _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
END
$$;
NOTICE: a: 10, b: 30, c: 50
NOTICE: _a: 100, _b: 40, _c: -500
NOTICE: a: 100, b: 40, c: -500
NOTICE: _a: 1000, _b: 50, _c: 5000
CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
a := a * 10;
b := b + 10;
c := c * -10;
END;
$$;
DO $$
DECLARE _a int; _b int; _c int;
BEGIN
_a := 10; _b := 30; _c := 50;
CALL test_proc8c(_a, _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
_a := 10; _b := 30; _c := 50;
CALL test_proc8c(_a, b => _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
END
$$;
NOTICE: a: 10, b: 30, c: 11
NOTICE: _a: 100, _b: 40, _c: 50
NOTICE: a: 10, b: 30, c: 11
NOTICE: _a: 100, _b: 40, _c: 50
-- transition variable assignment -- transition variable assignment
TRUNCATE test1; TRUNCATE test1;
CREATE FUNCTION triggerfunc1() RETURNS trigger CREATE FUNCTION triggerfunc1() RETURNS trigger
......
...@@ -2146,7 +2146,6 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ...@@ -2146,7 +2146,6 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
FuncExpr *funcexpr; FuncExpr *funcexpr;
int i; int i;
HeapTuple tuple; HeapTuple tuple;
int numargs PG_USED_FOR_ASSERTS_ONLY;
Oid *argtypes; Oid *argtypes;
char **argnames; char **argnames;
char *argmodes; char *argmodes;
...@@ -2169,11 +2168,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ...@@ -2169,11 +2168,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid)); tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid));
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid); elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid);
numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
Assert(numargs == list_length(funcexpr->args));
/* /*
* Construct row * Construct row
*/ */
...@@ -2192,16 +2189,36 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) ...@@ -2192,16 +2189,36 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
if (argmodes && argmodes[i] == PROARGMODE_INOUT) if (argmodes && argmodes[i] == PROARGMODE_INOUT)
{ {
if (IsA(n, Param))
{
Param *param = castNode(Param, n);
/* paramid is offset by 1 (see make_datum_param()) */
row->varnos[nfields++] = param->paramid - 1;
}
else if (IsA(n, NamedArgExpr))
{
NamedArgExpr *nexpr = castNode(NamedArgExpr, n);
Param *param; Param *param;
if (!IsA(n, Param)) if (!IsA(nexpr->arg, Param))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("argument %d is an output argument but is not writable", i + 1))); errmsg("argument %d is an output argument but is not writable", i + 1)));
param = castNode(Param, n); param = castNode(Param, nexpr->arg);
/* paramid is offset by 1 (see make_datum_param()) */
row->varnos[nfields++] = param->paramid - 1; /*
* Named arguments must be after positional arguments,
* so we can increase nfields.
*/
row->varnos[nexpr->argnumber] = param->paramid - 1;
nfields++;
}
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("argument %d is an output argument but is not writable", i + 1)));
} }
i++; i++;
} }
......
...@@ -142,6 +142,80 @@ $$; ...@@ -142,6 +142,80 @@ $$;
CALL test_proc7(100, -1, -1); CALL test_proc7(100, -1, -1);
-- named parameters and defaults
CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %', a, b;
a := a * 10;
b := b + 10;
END;
$$;
CALL test_proc8a(10, 20);
CALL test_proc8a(b => 20, a => 10);
DO $$
DECLARE _a int; _b int;
BEGIN
_a := 10; _b := 30;
CALL test_proc8a(_a, _b);
RAISE NOTICE '_a: %, _b: %', _a, _b;
CALL test_proc8a(b => _b, a => _a);
RAISE NOTICE '_a: %, _b: %', _a, _b;
END
$$;
CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
a := a * 10;
b := b + 10;
c := c * -10;
END;
$$;
DO $$
DECLARE _a int; _b int; _c int;
BEGIN
_a := 10; _b := 30; _c := 50;
CALL test_proc8b(_a, _b, _c);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
CALL test_proc8b(_a, c => _c, b => _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
END
$$;
CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
LANGUAGE plpgsql
AS $$
BEGIN
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
a := a * 10;
b := b + 10;
c := c * -10;
END;
$$;
DO $$
DECLARE _a int; _b int; _c int;
BEGIN
_a := 10; _b := 30; _c := 50;
CALL test_proc8c(_a, _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
_a := 10; _b := 30; _c := 50;
CALL test_proc8c(_a, b => _b);
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
END
$$;
-- transition variable assignment -- transition variable assignment
TRUNCATE test1; TRUNCATE test1;
......
...@@ -91,6 +91,31 @@ $$; ...@@ -91,6 +91,31 @@ $$;
ERROR: calling procedures with output arguments is not supported in SQL functions ERROR: calling procedures with output arguments is not supported in SQL functions
CONTEXT: SQL function "ptest4b" CONTEXT: SQL function "ptest4b"
DROP PROCEDURE ptest4a; DROP PROCEDURE ptest4a;
-- named and default parameters
CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(a, b);
INSERT INTO cp_test VALUES(c, b);
$$;
TRUNCATE cp_test;
CALL ptest5(10, 'Hello', 20);
CALL ptest5(10, 'Hello');
CALL ptest5(10, b => 'Hello');
CALL ptest5(b => 'Hello', a => 10);
SELECT * FROM cp_test;
a | b
-----+-------
10 | Hello
20 | Hello
10 | Hello
100 | Hello
10 | Hello
100 | Hello
10 | Hello
100 | Hello
(8 rows)
-- various error cases -- various error cases
CALL version(); -- error: not a procedure CALL version(); -- error: not a procedure
ERROR: version() is not a procedure ERROR: version() is not a procedure
......
...@@ -65,6 +65,25 @@ $$; ...@@ -65,6 +65,25 @@ $$;
DROP PROCEDURE ptest4a; DROP PROCEDURE ptest4a;
-- named and default parameters
CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(a, b);
INSERT INTO cp_test VALUES(c, b);
$$;
TRUNCATE cp_test;
CALL ptest5(10, 'Hello', 20);
CALL ptest5(10, 'Hello');
CALL ptest5(10, b => 'Hello');
CALL ptest5(b => 'Hello', a => 10);
SELECT * FROM cp_test;
-- various error cases -- various error cases
CALL version(); -- error: not a procedure CALL version(); -- error: not a procedure
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment