diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index aeb6c8fefc..ce34438e0c 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -2142,13 +2142,15 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) volatile LocalTransactionId before_lxid; LocalTransactionId after_lxid; volatile bool pushed_active_snap = false; - volatile int rc; + volatile int rc = -1; + bool plan_owner = false; /* PG_TRY to ensure we clear the plan link, if needed, on failure */ PG_TRY(); { - SPIPlanPtr plan = expr->plan; + SPIPlanPtr plan = expr->plan; ParamListInfo paramLI; + Node *node; if (plan == NULL) { @@ -2162,6 +2164,16 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) */ exec_prepare_plan(estate, expr, 0, estate->atomic); + /* + * Not saved plan should be explicitly released. Without it, any + * not recursive usage of CALL statemenst leak plan in SPI memory. + * The created plan can be reused when procedure is called recursively, + * and releasing plan can be done only in recursion root call, when + * expression has not assigned plan. Where a plan was created, then + * there plan can be released. + */ + plan_owner = true; + /* * The procedure call could end transactions, which would upset * the snapshot management in SPI_execute*, so don't let it do it. @@ -2184,18 +2196,17 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) */ if (stmt->is_call && stmt->target == NULL) { - Node *node; - FuncExpr *funcexpr; - HeapTuple func_tuple; - List *funcargs; - Oid *argtypes; - char **argnames; - char *argmodes; - MemoryContext oldcontext; - PLpgSQL_row *row; - int nfields; - int i; - ListCell *lc; + HeapTuple func_tuple; + MemoryContext oldcontext; + const FuncExpr *funcexpr; + const List *funcargs; + const Oid *argtypes; + const char **argnames; + const char *argmodes; + const ListCell *lc; + PLpgSQL_row *row; + int nfields; + int i; /* * Get the parsed CallStmt, and look up the called procedure @@ -2203,14 +2214,14 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) node = linitial_node(Query, ((CachedPlanSource *) linitial(plan->plancache_list))->query_list)->utilityStmt; if (node == NULL || !IsA(node, CallStmt)) - elog(ERROR, "query for CALL statement is not a CallStmt"); + elog(ERROR, "CALL statement is not a CallStmt, query \"%s\"", expr->query); funcexpr = ((CallStmt *) node)->funcexpr; func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcexpr->funcid)); if (!HeapTupleIsValid(func_tuple)) - elog(ERROR, "cache lookup failed for function %u", + elog(ERROR, "Cache lookup failed for function %u", funcexpr->funcid); /* @@ -2249,15 +2260,15 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) i = 0; foreach(lc, funcargs) { - Node *n = lfirst(lc); + node = lfirst(lc); if (argmodes && (argmodes[i] == PROARGMODE_INOUT || argmodes[i] == PROARGMODE_OUT)) { - if (IsA(n, Param)) + if (IsA(node, Param)) { - Param *param = (Param *) n; + const Param *param = (Param *) node; /* paramid is offset by 1 (see make_datum_param()) */ row->varnos[nfields++] = param->paramid - 1; @@ -2277,7 +2288,7 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) i + 1))); } } - i++; + ++i; } row->nfields = nfields; @@ -2314,12 +2325,23 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) } PG_END_TRY(); - if (expr->plan && !expr->plan->saved) - expr->plan = NULL; - if (rc < 0) elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s", expr->query, SPI_result_code_string(rc)); + if (SPI_processed == 1) + { + if (!stmt->target) + elog(ERROR, "DO statement returned a row, query \"%s\"", expr->query); + } + else if (SPI_processed > 1) + elog(ERROR, "Procedure call returned more than one row, query \"%s\"", expr->query); + + if (expr->plan && !expr->plan->saved) + { + if (plan_owner) + SPI_freeplan(expr->plan); + expr->plan = NULL; + } after_lxid = MyProc->lxid; @@ -2339,9 +2361,12 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) * If we are in a new transaction after the call, we need to build new * simple-expression infrastructure. */ - estate->simple_eval_estate = NULL; - estate->simple_eval_resowner = NULL; - plpgsql_create_econtext(estate); + if (SPI_processed == 1) + { + estate->simple_eval_estate = NULL; + estate->simple_eval_resowner = NULL; + plpgsql_create_econtext(estate); + } } /* @@ -2350,15 +2375,10 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt) */ if (SPI_processed == 1) { - SPITupleTable *tuptab = SPI_tuptable; - - if (!stmt->target) - elog(ERROR, "DO statement returned a row"); + const SPITupleTable *tuptab = SPI_tuptable; exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc); } - else if (SPI_processed > 1) - elog(ERROR, "procedure call returned more than one row"); exec_eval_cleanup(estate); SPI_freetuptable(SPI_tuptable);