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/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,8 +2227,31 @@ 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));
|
||||||
|
|
||||||
|
/* 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);
|
nargs = list_length(fexpr->args);
|
||||||
|
|
||||||
|
ReleaseSysCache(tp);
|
||||||
|
|
||||||
/* safety check; see ExecInitFunc() */
|
/* safety check; see ExecInitFunc() */
|
||||||
if (nargs > FUNC_MAX_ARGS)
|
if (nargs > FUNC_MAX_ARGS)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -2237,23 +2261,6 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
|
|||||||
FUNC_MAX_ARGS,
|
FUNC_MAX_ARGS,
|
||||||
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 */
|
/* 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,
|
|||||||
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,
|
|||||||
* 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 @@
|
|||||||
#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);
|
|||||||
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);
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
|
|
||||||
if (argmodes && argmodes[i] == PROARGMODE_INOUT)
|
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,
|
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);
|
|
||||||
/* paramid is offset by 1 (see make_datum_param()) */
|
|
||||||
row->varnos[nfields++] = param->paramid - 1;
|
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -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 @@ $$;
|
|||||||
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 @@ $$;
|
|||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user