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>
This commit is contained in:
parent
7c44c46deb
commit
a8677e3ff6
@ -52,6 +52,7 @@
|
||||
#include "executor/execdesc.h"
|
||||
#include "executor/executor.h"
|
||||
#include "miscadmin.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
@ -2226,8 +2227,31 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid));
|
||||
|
||||
/* Prep the context object we'll pass to the procedure */
|
||||
callcontext = makeNode(CallContext);
|
||||
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
|
||||
* way the GUC stacking works: The transaction boundary would have to pop
|
||||
* the proconfig setting off the stack. That restriction could be lifted
|
||||
* by redesigning the GUC nesting mechanism a bit.
|
||||
*/
|
||||
if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL))
|
||||
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);
|
||||
|
||||
/* safety check; see ExecInitFunc() */
|
||||
if (nargs > FUNC_MAX_ARGS)
|
||||
ereport(ERROR,
|
||||
@ -2237,23 +2261,6 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
|
||||
FUNC_MAX_ARGS,
|
||||
FUNC_MAX_ARGS)));
|
||||
|
||||
/* Prep the context object we'll pass to the procedure */
|
||||
callcontext = makeNode(CallContext);
|
||||
callcontext->atomic = atomic;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* the proconfig setting off the stack. That restriction could be lifted
|
||||
* 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))
|
||||
callcontext->atomic = true;
|
||||
ReleaseSysCache(tp);
|
||||
|
||||
/* Initialize function call structure */
|
||||
InvokeFunctionExecuteHook(fexpr->funcid);
|
||||
fmgr_info(fexpr->funcid, &flinfo);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -152,6 +152,93 @@ CALL test_proc7(100, -1, -1);
|
||||
0 | 1
|
||||
(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
|
||||
TRUNCATE test1;
|
||||
CREATE FUNCTION triggerfunc1() RETURNS trigger
|
||||
|
@ -2146,7 +2146,6 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
|
||||
FuncExpr *funcexpr;
|
||||
int i;
|
||||
HeapTuple tuple;
|
||||
int numargs PG_USED_FOR_ASSERTS_ONLY;
|
||||
Oid *argtypes;
|
||||
char **argnames;
|
||||
char *argmodes;
|
||||
@ -2169,11 +2168,9 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
|
||||
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);
|
||||
get_func_arg_info(tuple, &argtypes, &argnames, &argmodes);
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
Assert(numargs == list_length(funcexpr->args));
|
||||
|
||||
/*
|
||||
* Construct row
|
||||
*/
|
||||
@ -2192,16 +2189,36 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
|
||||
|
||||
if (argmodes && argmodes[i] == PROARGMODE_INOUT)
|
||||
{
|
||||
Param *param;
|
||||
if (IsA(n, Param))
|
||||
{
|
||||
Param *param = castNode(Param, n);
|
||||
|
||||
if (!IsA(n, Param))
|
||||
/* 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;
|
||||
|
||||
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, nexpr->arg);
|
||||
|
||||
/*
|
||||
* 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)));
|
||||
|
||||
param = castNode(Param, n);
|
||||
/* paramid is offset by 1 (see make_datum_param()) */
|
||||
row->varnos[nfields++] = param->paramid - 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
@ -142,6 +142,80 @@ $$;
|
||||
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
|
||||
|
||||
TRUNCATE test1;
|
||||
|
@ -91,6 +91,31 @@ $$;
|
||||
ERROR: calling procedures with output arguments is not supported in SQL functions
|
||||
CONTEXT: SQL function "ptest4b"
|
||||
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
|
||||
CALL version(); -- error: not a procedure
|
||||
ERROR: version() is not a procedure
|
||||
|
@ -65,6 +65,25 @@ $$;
|
||||
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
|
||||
|
||||
CALL version(); -- error: not a procedure
|
||||
|
Loading…
x
Reference in New Issue
Block a user