Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a8677e3

Browse files
committed
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>
1 parent 7c44c46 commit a8677e3

File tree

8 files changed

+258
-28
lines changed

8 files changed

+258
-28
lines changed

src/backend/commands/functioncmds.c

+21-14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "executor/execdesc.h"
5353
#include "executor/executor.h"
5454
#include "miscadmin.h"
55+
#include "optimizer/clauses.h"
5556
#include "optimizer/var.h"
5657
#include "parser/parse_coerce.h"
5758
#include "parser/parse_collate.h"
@@ -2226,34 +2227,40 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
22262227
if (aclresult != ACLCHECK_OK)
22272228
aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid));
22282229

2229-
nargs = list_length(fexpr->args);
2230-
2231-
/* safety check; see ExecInitFunc() */
2232-
if (nargs > FUNC_MAX_ARGS)
2233-
ereport(ERROR,
2234-
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
2235-
errmsg_plural("cannot pass more than %d argument to a procedure",
2236-
"cannot pass more than %d arguments to a procedure",
2237-
FUNC_MAX_ARGS,
2238-
FUNC_MAX_ARGS)));
2239-
22402230
/* Prep the context object we'll pass to the procedure */
22412231
callcontext = makeNode(CallContext);
22422232
callcontext->atomic = atomic;
22432233

2234+
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
2235+
if (!HeapTupleIsValid(tp))
2236+
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
2237+
22442238
/*
22452239
* If proconfig is set we can't allow transaction commands because of the
22462240
* way the GUC stacking works: The transaction boundary would have to pop
22472241
* the proconfig setting off the stack. That restriction could be lifted
22482242
* by redesigning the GUC nesting mechanism a bit.
22492243
*/
2250-
tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
2251-
if (!HeapTupleIsValid(tp))
2252-
elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
22532244
if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL))
22542245
callcontext->atomic = true;
2246+
2247+
/*
2248+
* Expand named arguments, defaults, etc.
2249+
*/
2250+
fexpr->args = expand_function_arguments(fexpr->args, fexpr->funcresulttype, tp);
2251+
nargs = list_length(fexpr->args);
2252+
22552253
ReleaseSysCache(tp);
22562254

2255+
/* safety check; see ExecInitFunc() */
2256+
if (nargs > FUNC_MAX_ARGS)
2257+
ereport(ERROR,
2258+
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
2259+
errmsg_plural("cannot pass more than %d argument to a procedure",
2260+
"cannot pass more than %d arguments to a procedure",
2261+
FUNC_MAX_ARGS,
2262+
FUNC_MAX_ARGS)));
2263+
22572264
/* Initialize function call structure */
22582265
InvokeFunctionExecuteHook(fexpr->funcid);
22592266
fmgr_info(fexpr->funcid, &flinfo);

src/backend/optimizer/util/clauses.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ static Expr *simplify_function(Oid funcid,
130130
Oid result_collid, Oid input_collid, List **args_p,
131131
bool funcvariadic, bool process_args, bool allow_non_const,
132132
eval_const_expressions_context *context);
133-
static List *expand_function_arguments(List *args, Oid result_type,
134-
HeapTuple func_tuple);
135133
static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
136134
static List *add_function_defaults(List *args, HeapTuple func_tuple);
137135
static List *fetch_function_defaults(HeapTuple func_tuple);
@@ -4112,7 +4110,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
41124110
* cases it handles should never occur there. This should be OK since it
41134111
* will fall through very quickly if there's nothing to do.
41144112
*/
4115-
static List *
4113+
List *
41164114
expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
41174115
{
41184116
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);

src/include/optimizer/clauses.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
#ifndef CLAUSES_H
1515
#define CLAUSES_H
1616

17+
#include "access/htup.h"
1718
#include "nodes/relation.h"
1819

19-
2020
#define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr))
2121
#define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr))
2222

@@ -85,4 +85,7 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
8585
extern Query *inline_set_returning_function(PlannerInfo *root,
8686
RangeTblEntry *rte);
8787

88+
extern List *expand_function_arguments(List *args, Oid result_type,
89+
HeapTuple func_tuple);
90+
8891
#endif /* CLAUSES_H */

src/pl/plpgsql/src/expected/plpgsql_call.out

+87
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,93 @@ CALL test_proc7(100, -1, -1);
152152
0 | 1
153153
(1 row)
154154

155+
-- named parameters and defaults
156+
CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
157+
LANGUAGE plpgsql
158+
AS $$
159+
BEGIN
160+
RAISE NOTICE 'a: %, b: %', a, b;
161+
a := a * 10;
162+
b := b + 10;
163+
END;
164+
$$;
165+
CALL test_proc8a(10, 20);
166+
NOTICE: a: 10, b: 20
167+
a | b
168+
-----+----
169+
100 | 30
170+
(1 row)
171+
172+
CALL test_proc8a(b => 20, a => 10);
173+
NOTICE: a: 10, b: 20
174+
a | b
175+
-----+----
176+
100 | 30
177+
(1 row)
178+
179+
DO $$
180+
DECLARE _a int; _b int;
181+
BEGIN
182+
_a := 10; _b := 30;
183+
CALL test_proc8a(_a, _b);
184+
RAISE NOTICE '_a: %, _b: %', _a, _b;
185+
CALL test_proc8a(b => _b, a => _a);
186+
RAISE NOTICE '_a: %, _b: %', _a, _b;
187+
END
188+
$$;
189+
NOTICE: a: 10, b: 30
190+
NOTICE: _a: 100, _b: 40
191+
NOTICE: a: 100, b: 40
192+
NOTICE: _a: 1000, _b: 50
193+
CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
194+
LANGUAGE plpgsql
195+
AS $$
196+
BEGIN
197+
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
198+
a := a * 10;
199+
b := b + 10;
200+
c := c * -10;
201+
END;
202+
$$;
203+
DO $$
204+
DECLARE _a int; _b int; _c int;
205+
BEGIN
206+
_a := 10; _b := 30; _c := 50;
207+
CALL test_proc8b(_a, _b, _c);
208+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
209+
CALL test_proc8b(_a, c => _c, b => _b);
210+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
211+
END
212+
$$;
213+
NOTICE: a: 10, b: 30, c: 50
214+
NOTICE: _a: 100, _b: 40, _c: -500
215+
NOTICE: a: 100, b: 40, c: -500
216+
NOTICE: _a: 1000, _b: 50, _c: 5000
217+
CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
218+
LANGUAGE plpgsql
219+
AS $$
220+
BEGIN
221+
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
222+
a := a * 10;
223+
b := b + 10;
224+
c := c * -10;
225+
END;
226+
$$;
227+
DO $$
228+
DECLARE _a int; _b int; _c int;
229+
BEGIN
230+
_a := 10; _b := 30; _c := 50;
231+
CALL test_proc8c(_a, _b);
232+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
233+
_a := 10; _b := 30; _c := 50;
234+
CALL test_proc8c(_a, b => _b);
235+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
236+
END
237+
$$;
238+
NOTICE: a: 10, b: 30, c: 11
239+
NOTICE: _a: 100, _b: 40, _c: 50
240+
NOTICE: a: 10, b: 30, c: 11
241+
NOTICE: _a: 100, _b: 40, _c: 50
155242
-- transition variable assignment
156243
TRUNCATE test1;
157244
CREATE FUNCTION triggerfunc1() RETURNS trigger

src/pl/plpgsql/src/pl_exec.c

+27-10
Original file line numberDiff line numberDiff line change
@@ -2146,7 +2146,6 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
21462146
FuncExpr *funcexpr;
21472147
int i;
21482148
HeapTuple tuple;
2149-
int numargs PG_USED_FOR_ASSERTS_ONLY;
21502149
Oid *argtypes;
21512150
char **argnames;
21522151
char *argmodes;
@@ -2169,11 +2168,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
21692168
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid));
21702169
if (!HeapTupleIsValid(tuple))
21712170
elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid);
2172-
numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
2171+
get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
21732172
ReleaseSysCache(tuple);
21742173

2175-
Assert(numargs == list_length(funcexpr->args));
2176-
21772174
/*
21782175
* Construct row
21792176
*/
@@ -2192,16 +2189,36 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
21922189

21932190
if (argmodes && argmodes[i] == PROARGMODE_INOUT)
21942191
{
2195-
Param *param;
2192+
if (IsA(n, Param))
2193+
{
2194+
Param *param = castNode(Param, n);
2195+
2196+
/* paramid is offset by 1 (see make_datum_param()) */
2197+
row->varnos[nfields++] = param->paramid - 1;
2198+
}
2199+
else if (IsA(n, NamedArgExpr))
2200+
{
2201+
NamedArgExpr *nexpr = castNode(NamedArgExpr, n);
2202+
Param *param;
2203+
2204+
if (!IsA(nexpr->arg, Param))
2205+
ereport(ERROR,
2206+
(errcode(ERRCODE_SYNTAX_ERROR),
2207+
errmsg("argument %d is an output argument but is not writable", i + 1)));
21962208

2197-
if (!IsA(n, Param))
2209+
param = castNode(Param, nexpr->arg);
2210+
2211+
/*
2212+
* Named arguments must be after positional arguments,
2213+
* so we can increase nfields.
2214+
*/
2215+
row->varnos[nexpr->argnumber] = param->paramid - 1;
2216+
nfields++;
2217+
}
2218+
else
21982219
ereport(ERROR,
21992220
(errcode(ERRCODE_SYNTAX_ERROR),
22002221
errmsg("argument %d is an output argument but is not writable", i + 1)));
2201-
2202-
param = castNode(Param, n);
2203-
/* paramid is offset by 1 (see make_datum_param()) */
2204-
row->varnos[nfields++] = param->paramid - 1;
22052222
}
22062223
i++;
22072224
}

src/pl/plpgsql/src/sql/plpgsql_call.sql

+74
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,80 @@ $$;
142142
CALL test_proc7(100, -1, -1);
143143

144144

145+
-- named parameters and defaults
146+
147+
CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
148+
LANGUAGE plpgsql
149+
AS $$
150+
BEGIN
151+
RAISE NOTICE 'a: %, b: %', a, b;
152+
a := a * 10;
153+
b := b + 10;
154+
END;
155+
$$;
156+
157+
CALL test_proc8a(10, 20);
158+
CALL test_proc8a(b => 20, a => 10);
159+
160+
DO $$
161+
DECLARE _a int; _b int;
162+
BEGIN
163+
_a := 10; _b := 30;
164+
CALL test_proc8a(_a, _b);
165+
RAISE NOTICE '_a: %, _b: %', _a, _b;
166+
CALL test_proc8a(b => _b, a => _a);
167+
RAISE NOTICE '_a: %, _b: %', _a, _b;
168+
END
169+
$$;
170+
171+
172+
CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
173+
LANGUAGE plpgsql
174+
AS $$
175+
BEGIN
176+
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
177+
a := a * 10;
178+
b := b + 10;
179+
c := c * -10;
180+
END;
181+
$$;
182+
183+
DO $$
184+
DECLARE _a int; _b int; _c int;
185+
BEGIN
186+
_a := 10; _b := 30; _c := 50;
187+
CALL test_proc8b(_a, _b, _c);
188+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
189+
CALL test_proc8b(_a, c => _c, b => _b);
190+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
191+
END
192+
$$;
193+
194+
195+
CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
196+
LANGUAGE plpgsql
197+
AS $$
198+
BEGIN
199+
RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
200+
a := a * 10;
201+
b := b + 10;
202+
c := c * -10;
203+
END;
204+
$$;
205+
206+
DO $$
207+
DECLARE _a int; _b int; _c int;
208+
BEGIN
209+
_a := 10; _b := 30; _c := 50;
210+
CALL test_proc8c(_a, _b);
211+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
212+
_a := 10; _b := 30; _c := 50;
213+
CALL test_proc8c(_a, b => _b);
214+
RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
215+
END
216+
$$;
217+
218+
145219
-- transition variable assignment
146220

147221
TRUNCATE test1;

src/test/regress/expected/create_procedure.out

+25
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,31 @@ $$;
9191
ERROR: calling procedures with output arguments is not supported in SQL functions
9292
CONTEXT: SQL function "ptest4b"
9393
DROP PROCEDURE ptest4a;
94+
-- named and default parameters
95+
CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
96+
LANGUAGE SQL
97+
AS $$
98+
INSERT INTO cp_test VALUES(a, b);
99+
INSERT INTO cp_test VALUES(c, b);
100+
$$;
101+
TRUNCATE cp_test;
102+
CALL ptest5(10, 'Hello', 20);
103+
CALL ptest5(10, 'Hello');
104+
CALL ptest5(10, b => 'Hello');
105+
CALL ptest5(b => 'Hello', a => 10);
106+
SELECT * FROM cp_test;
107+
a | b
108+
-----+-------
109+
10 | Hello
110+
20 | Hello
111+
10 | Hello
112+
100 | Hello
113+
10 | Hello
114+
100 | Hello
115+
10 | Hello
116+
100 | Hello
117+
(8 rows)
118+
94119
-- various error cases
95120
CALL version(); -- error: not a procedure
96121
ERROR: version() is not a procedure

src/test/regress/sql/create_procedure.sql

+19
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@ $$;
6565
DROP PROCEDURE ptest4a;
6666

6767

68+
-- named and default parameters
69+
70+
CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
71+
LANGUAGE SQL
72+
AS $$
73+
INSERT INTO cp_test VALUES(a, b);
74+
INSERT INTO cp_test VALUES(c, b);
75+
$$;
76+
77+
TRUNCATE cp_test;
78+
79+
CALL ptest5(10, 'Hello', 20);
80+
CALL ptest5(10, 'Hello');
81+
CALL ptest5(10, b => 'Hello');
82+
CALL ptest5(b => 'Hello', a => 10);
83+
84+
SELECT * FROM cp_test;
85+
86+
6887
-- various error cases
6988

7089
CALL version(); -- error: not a procedure

0 commit comments

Comments
 (0)