From e245c91211adb26be7cfdaeed2977411a003622f Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 13 Jan 2010 16:57:03 +0000
Subject: [PATCH] Fix bug #5269: ResetPlanCache mustn't invalidate cached
 utility statements, especially not ROLLBACK.  ROLLBACK might need to be
 executed in an already aborted transaction, when there is no safe way to
 revalidate the plan.  But in general there's no point in marking utility
 statements invalid, since they have no plans in the normal sense of the word;
 so we might as well work a bit harder here to avoid future revalidation
 cycles.

Back-patch to 8.4, where the bug was introduced.
---
 src/backend/utils/cache/plancache.c | 55 +++++++++++++++++++++++++----
 1 file changed, 49 insertions(+), 6 deletions(-)

diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 0223a9ebc6..3b22f40613 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -35,7 +35,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.27.2.1 2009/07/14 15:37:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.27.2.2 2010/01/13 16:57:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1050,14 +1050,57 @@ PlanCacheSysCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
 void
 ResetPlanCache(void)
 {
-	ListCell   *lc;
+	ListCell   *lc1;
 
-	foreach(lc, cached_plans_list)
+	foreach(lc1, cached_plans_list)
 	{
-		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
 		CachedPlan *plan = plansource->plan;
+		ListCell   *lc2;
 
-		if (plan)
-			plan->dead = true;
+		/* No work if it's already invalidated */
+		if (!plan || plan->dead)
+			continue;
+
+		/*
+		 * We *must not* mark transaction control statements as dead,
+		 * particularly not ROLLBACK, because they may need to be executed in
+		 * aborted transactions when we can't revalidate them (cf bug #5269).
+		 * In general there is no point in invalidating utility statements
+		 * since they have no plans anyway.  So mark it dead only if it
+		 * contains at least one non-utility statement.
+		 */
+		if (plan->fully_planned)
+		{
+			/* Search statement list for non-utility statements */
+			foreach(lc2, plan->stmt_list)
+			{
+				PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+
+				Assert(!IsA(plannedstmt, Query));
+				if (IsA(plannedstmt, PlannedStmt))
+				{
+					/* non-utility statement, so invalidate */
+					plan->dead = true;
+					break;		/* out of stmt_list scan */
+				}
+			}
+		}
+		else
+		{
+			/* Search Query list for non-utility statements */
+			foreach(lc2, plan->stmt_list)
+			{
+				Query	   *query = (Query *) lfirst(lc2);
+
+				Assert(IsA(query, Query));
+				if (query->commandType != CMD_UTILITY)
+				{
+					/* non-utility statement, so invalidate */
+					plan->dead = true;
+					break;		/* out of stmt_list scan */
+				}
+			}
+		}
 	}
 }