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);
