čt 1. 11. 2018 v 14:31 odesílatel Pavel Stehule <pavel.steh...@gmail.com> napsal:
> Cleaned patch with regress tests > > minor cleaning Regards Pavel
diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out index 547ca22a55..8762e1335c 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -276,3 +276,43 @@ DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc3; DROP PROCEDURE test_proc4; DROP TABLE test1; +CREATE TABLE test_call_proc(key serial, name text); +CREATE OR REPLACE PROCEDURE p1(v_cnt int, v_ResultSet inout refcursor = NULL) +AS $$ +BEGIN + INSERT INTO test_call_proc(name) VALUES('name test'); + OPEN v_ResultSet FOR SELECT * FROM test_call_proc; +END +$$ LANGUAGE plpgsql; +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(v_cnt:=v_cnt, v_ResultSet := v_ResultSet); + RAISE NOTICE '%', v_ResultSet; +END; +$$; +NOTICE: <unnamed portal 1> +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(10, v_ResultSet := v_ResultSet); + RAISE NOTICE '%', v_ResultSet; +END; +$$; +NOTICE: <unnamed portal 2> +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(v_ResultSet := v_ResultSet, v_cnt:=v_cnt); + RAISE NOTICE '%', v_ResultSet; +END; +$$; +NOTICE: <unnamed portal 3> +DROP PROCEDURE p1; +DROP TABLE test_call_proc; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 45526383f2..c737a66c54 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -2168,10 +2168,10 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) */ if (!stmt->target) { + Form_pg_proc funcform; Node *node; ListCell *lc; FuncExpr *funcexpr; - int i; HeapTuple tuple; Oid *argtypes; char **argnames; @@ -2179,6 +2179,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) MemoryContext oldcontext; PLpgSQL_row *row; int nfields; + int pronargs; /* * Get the original CallStmt @@ -2196,6 +2197,8 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcexpr->funcid); get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); + funcform = (Form_pg_proc) GETSTRUCT(tuple); + pronargs = funcform->pronargs; ReleaseSysCache(tuple); /* @@ -2210,45 +2213,74 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) row->varnos = palloc(sizeof(int) * FUNC_MAX_ARGS); nfields = 0; - i = 0; - foreach(lc, funcexpr->args) + + /* + * The argmodes can be in different order than funcexpr->args due + * named args. When we should to check INOUT parameters and prepare + * target variable, we should to reorder a arguments first. This is + * similar code to reorder_function_arguments. In this part a default + * values are not necessary. + */ + if (argmodes) { - Node *n = lfirst(lc); + Node *argarray[FUNC_MAX_ARGS]; + int nargsprovided = list_length(funcexpr->args); + int i = 0; + + MemSet(argarray, 0, pronargs * sizeof(Node *)); - if (argmodes && argmodes[i] == PROARGMODE_INOUT) + foreach(lc, funcexpr->args) { - if (IsA(n, Param)) - { - Param *param = castNode(Param, n); + Node *n = lfirst(lc); - /* paramid is offset by 1 (see make_datum_param()) */ - row->varnos[nfields++] = param->paramid - 1; - } - else if (IsA(n, NamedArgExpr)) + if (IsA(n, NamedArgExpr)) { NamedArgExpr *nexpr = castNode(NamedArgExpr, n); - Param *param; + argarray[nexpr->argnumber] = (Node *) nexpr->arg; + } + else + argarray[i++] = n; + } - if (!IsA(nexpr->arg, Param)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("argument %d is an output argument but is not writable", i + 1))); + Assert(nargsprovided <= pronargs); - param = castNode(Param, nexpr->arg); + for (i = 0; i < pronargs; i++) + { + Node *n = argarray[i]; + if (argmodes[i] == PROARGMODE_INOUT) + { /* - * Named arguments must be after positional arguments, - * so we can increase nfields. + * Empty positions are related to default values. The INOUT defaults + * are allowed, only if after are not any other parameter. */ - row->varnos[nexpr->argnumber] = param->paramid - 1; - nfields++; + if (!n) + { + int j; + + for (j = i + 1; j < pronargs; j++) + { + if (argarray[j]) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("argument %i with default values is output argument but it is not writeable", i + 1))); + + /* there are not any other parameter */ + break; + } + else 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 + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("argument %d is an output argument but is not writable", i + 1))); } - else - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("argument %d is an output argument but is not writable", i + 1))); } - i++; } row->nfields = nfields; diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql index 29e85803e7..2a5070c8f6 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_call.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql @@ -251,3 +251,48 @@ DROP PROCEDURE test_proc3; DROP PROCEDURE test_proc4; DROP TABLE test1; + + +CREATE TABLE test_call_proc(key serial, name text); + +CREATE OR REPLACE PROCEDURE p1(v_cnt int, v_ResultSet inout refcursor = NULL) +AS $$ +BEGIN + INSERT INTO test_call_proc(name) VALUES('name test'); + OPEN v_ResultSet FOR SELECT * FROM test_call_proc; +END +$$ LANGUAGE plpgsql; + +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(v_cnt:=v_cnt, v_ResultSet := v_ResultSet); + RAISE NOTICE '%', v_ResultSet; +END; +$$; + +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(10, v_ResultSet := v_ResultSet); + RAISE NOTICE '%', v_ResultSet; +END; +$$; + +DO $$ +DECLARE + v_ResultSet refcursor; + v_cnt integer; +BEGIN + CALL p1(v_ResultSet := v_ResultSet, v_cnt:=v_cnt); + RAISE NOTICE '%', v_ResultSet; +END; +$$; + +DROP PROCEDURE p1; + +DROP TABLE test_call_proc;