diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index a027b19744..abdfa249c0 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -2212,11 +2212,9 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic) * commits that might occur inside the procedure. */ void -ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic) +ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic) { - List *targs; ListCell *lc; - Node *node; FuncExpr *fexpr; int nargs; int i; @@ -2228,24 +2226,8 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic) ExprContext *econtext; HeapTuple tp; - /* We need to do parse analysis on the procedure call and its arguments */ - targs = NIL; - foreach(lc, stmt->funccall->args) - { - targs = lappend(targs, transformExpr(pstate, - (Node *) lfirst(lc), - EXPR_KIND_CALL_ARGUMENT)); - } - - node = ParseFuncOrColumn(pstate, - stmt->funccall->funcname, - targs, - pstate->p_last_srf, - stmt->funccall, - true, - stmt->funccall->location); - - fexpr = castNode(FuncExpr, node); + fexpr = stmt->funcexpr; + Assert(fexpr); aclresult = pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE); if (aclresult != ACLCHECK_OK) @@ -2289,6 +2271,7 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic) * we can't free this context till the procedure returns. */ estate = CreateExecutorState(); + estate->es_param_list_info = params; econtext = CreateExprContext(estate); i = 0; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 82255b0d1d..266a3ef8ef 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3231,6 +3231,7 @@ _copyCallStmt(const CallStmt *from) CallStmt *newnode = makeNode(CallStmt); COPY_NODE_FIELD(funccall); + COPY_NODE_FIELD(funcexpr); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b9bc8e38d7..bbffc87842 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1206,6 +1206,7 @@ static bool _equalCallStmt(const CallStmt *a, const CallStmt *b) { COMPARE_NODE_FIELD(funccall); + COMPARE_NODE_FIELD(funcexpr); return true; } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 5b3a610cf9..c3a9617f67 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -36,6 +36,8 @@ #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_cte.h" +#include "parser/parse_expr.h" +#include "parser/parse_func.h" #include "parser/parse_oper.h" #include "parser/parse_param.h" #include "parser/parse_relation.h" @@ -74,6 +76,8 @@ static Query *transformExplainStmt(ParseState *pstate, ExplainStmt *stmt); static Query *transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt); +static Query *transformCallStmt(ParseState *pstate, + CallStmt *stmt); static void transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, bool pushedDown); #ifdef RAW_EXPRESSION_COVERAGE_TEST @@ -318,6 +322,10 @@ transformStmt(ParseState *pstate, Node *parseTree) (CreateTableAsStmt *) parseTree); break; + case T_CallStmt: + result = transformCallStmt(pstate, + (CallStmt *) parseTree); + default: /* @@ -2571,6 +2579,43 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) return result; } +/* + * transform a CallStmt + * + * We need to do parse analysis on the procedure call and its arguments. + */ +static Query * +transformCallStmt(ParseState *pstate, CallStmt *stmt) +{ + List *targs; + ListCell *lc; + Node *node; + Query *result; + + targs = NIL; + foreach(lc, stmt->funccall->args) + { + targs = lappend(targs, transformExpr(pstate, + (Node *) lfirst(lc), + EXPR_KIND_CALL_ARGUMENT)); + } + + node = ParseFuncOrColumn(pstate, + stmt->funccall->funcname, + targs, + pstate->p_last_srf, + stmt->funccall, + true, + stmt->funccall->location); + + stmt->funcexpr = castNode(FuncExpr, node); + + result = makeNode(Query); + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + return result; +} /* * Produce a string representation of a LockClauseStrength value. diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 8c23ee53e2..f78efdf359 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -660,7 +660,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; case T_CallStmt: - ExecuteCallStmt(pstate, castNode(CallStmt, parsetree), + ExecuteCallStmt(castNode(CallStmt, parsetree), params, (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock())); break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index f510f40945..c829abfea7 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -15,6 +15,7 @@ #define DEFREM_H #include "catalog/objectaddress.h" +#include "nodes/params.h" #include "nodes/parsenodes.h" #include "utils/array.h" @@ -61,7 +62,7 @@ extern void DropTransformById(Oid transformOid); extern void IsThereFunctionInNamespace(const char *proname, int pronargs, oidvector *proargtypes, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt, bool atomic); -extern void ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic); +extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok); extern void interpret_function_parameter_list(ParseState *pstate, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index c7a43b8933..ac292bc6e7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2814,7 +2814,8 @@ typedef struct InlineCodeBlock typedef struct CallStmt { NodeTag type; - FuncCall *funccall; + FuncCall *funccall; /* from the parser */ + FuncExpr *funcexpr; /* transformed */ } CallStmt; typedef struct CallContext diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out index d0f35163bc..e2442c603c 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -35,7 +35,26 @@ SELECT * FROM test1; 55 (1 row) +-- nested CALL +TRUNCATE TABLE test1; +CREATE PROCEDURE test_proc4(y int) +LANGUAGE plpgsql +AS $$ +BEGIN + CALL test_proc3(y); + CALL test_proc3($1); +END; +$$; +CALL test_proc4(66); +SELECT * FROM test1; + a +---- + 66 + 66 +(2 rows) + DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; +DROP PROCEDURE test_proc4; DROP TABLE test1; diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql index 38fd220e8f..321ed43af8 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_call.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql @@ -40,8 +40,26 @@ CALL test_proc3(55); SELECT * FROM test1; +-- nested CALL +TRUNCATE TABLE test1; + +CREATE PROCEDURE test_proc4(y int) +LANGUAGE plpgsql +AS $$ +BEGIN + CALL test_proc3(y); + CALL test_proc3($1); +END; +$$; + +CALL test_proc4(66); + +SELECT * FROM test1; + + DROP PROCEDURE test_proc1; DROP PROCEDURE test_proc2; DROP PROCEDURE test_proc3; +DROP PROCEDURE test_proc4; DROP TABLE test1; diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out index e7bede24fa..182b325ea1 100644 --- a/src/test/regress/expected/create_procedure.out +++ b/src/test/regress/expected/create_procedure.out @@ -55,6 +55,22 @@ AS $$ SELECT 5; $$; CALL ptest2(); +-- nested CALL +TRUNCATE cp_test; +CREATE PROCEDURE ptest3(y text) +LANGUAGE SQL +AS $$ +CALL ptest1(y); +CALL ptest1($1); +$$; +CALL ptest3('b'); +SELECT * FROM cp_test; + a | b +---+--- + 1 | b + 1 | b +(2 rows) + -- various error cases CALL version(); -- error: not a procedure ERROR: version() is not a procedure diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql index 774c12ee34..52318bf2a6 100644 --- a/src/test/regress/sql/create_procedure.sql +++ b/src/test/regress/sql/create_procedure.sql @@ -31,6 +31,21 @@ $$; CALL ptest2(); +-- nested CALL +TRUNCATE cp_test; + +CREATE PROCEDURE ptest3(y text) +LANGUAGE SQL +AS $$ +CALL ptest1(y); +CALL ptest1($1); +$$; + +CALL ptest3('b'); + +SELECT * FROM cp_test; + + -- various error cases CALL version(); -- error: not a procedure