#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"
if (aclresult != ACLCHECK_OK)
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 */
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.
*/
- 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;
+
+ /*
+ * 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,
+ (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 */
InvokeFunctionExecuteHook(fexpr->funcid);
fmgr_info(fexpr->funcid, &flinfo);
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);
* 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);
#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))
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 */
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
FuncExpr *funcexpr;
int i;
HeapTuple tuple;
- int numargs PG_USED_FOR_ASSERTS_ONLY;
Oid *argtypes;
char **argnames;
char *argmodes;
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
*/
if (argmodes && argmodes[i] == PROARGMODE_INOUT)
{
- Param *param;
+ 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;
+
+ if (!IsA(nexpr->arg, Param))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("argument %d is an output argument but is not writable", i + 1)));
- if (!IsA(n, Param))
+ 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++;
}
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;
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
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