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

Lines changed: 21 additions & 14 deletions
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 87 additions & 0 deletions
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

Lines changed: 27 additions & 10 deletions
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

Lines changed: 74 additions & 0 deletions
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

Lines changed: 25 additions & 0 deletions
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

Lines changed: 19 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy