diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1617dedcef8596e1cb82bc8bf6e418fe1dafe0f4..37647e57398ff53c513b61b22ca5c0fab37399bb 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.198 2010/01/02 16:57:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.199 2010/01/15 22:36:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -158,19 +158,19 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 			 errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
 
 	/*
-	 * Run parse analysis and rewrite.	Note this also acquires sufficient
-	 * locks on the source table(s).
+	 * Parse analysis was done already, but we still have to run the rule
+	 * rewriter.  We do not do AcquireRewriteLocks: we assume the query
+	 * either came straight from the parser, or suitable locks were
+	 * acquired by plancache.c.
 	 *
-	 * Because the parser and planner tend to scribble on their input, we make
+	 * Because the rewriter and planner tend to scribble on the input, we make
 	 * a preliminary copy of the source querytree.	This prevents problems in
 	 * the case that the EXPLAIN is in a portal or plpgsql function and is
 	 * executed repeatedly.  (See also the same hack in DECLARE CURSOR and
 	 * PREPARE.)  XXX FIXME someday.
 	 */
-	rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query),
-											  queryString,
-											  (ParserSetupHook) setupParserWithParamList,
-											  params);
+	Assert(IsA(stmt->query, Query));
+	rewritten = QueryRewrite((Query *) copyObject(stmt->query));
 
 	/* emit opening boilerplate */
 	ExplainBeginOutput(&es);
@@ -248,6 +248,7 @@ ExplainResultDesc(ExplainStmt *stmt)
 			char   *p = defGetString(opt);
 
 			xml = (strcmp(p, "xml") == 0);
+			/* don't "break", as ExplainQuery will use the last value */
 		}
 	}
 
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c
index 136f40ea549a3dc4babdbf0dbc646f906d1cf836..ef17a9bb32184b445c9b36df3cd782099bd0c7b7 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.13 2010/01/02 16:57:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.14 2010/01/15 22:36:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -75,47 +75,3 @@ copyParamList(ParamListInfo from)
 
 	return retval;
 }
-
-/*
- * Set up the parser to treat the given list of run-time parameters
- * as available external parameters during parsing of a new query.
- *
- * Note that the parser doesn't actually care about the *values* of the given
- * parameters, only about their *types*.  Also, the code that originally
- * provided the ParamListInfo may have provided a setupHook, which should
- * override applying parse_fixed_parameters().
- */
-void
-setupParserWithParamList(struct ParseState *pstate,
-						 ParamListInfo params)
-{
-	if (params == NULL)			/* no params, nothing to do */
-		return;
-
-	/* If there is a parserSetup hook, it gets to do this */
-	if (params->parserSetup != NULL)
-	{
-		(*params->parserSetup) (pstate, params->parserSetupArg);
-		return;
-	}
-
-	/* Else, treat any available parameters as being of fixed type */
-	if (params->numParams > 0)
-	{
-		Oid		   *ptypes;
-		int			i;
-
-		ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
-		for (i = 0; i < params->numParams; i++)
-		{
-			ParamExternData *prm = &params->params[i];
-
-			/* give hook a chance in case parameter is dynamic */
-			if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
-				(*params->paramFetch) (params, i+1);
-
-			ptypes[i] = prm->ptype;
-		}
-		parse_fixed_parameters(pstate, ptypes, params->numParams);
-	}
-}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 2c33e6acb980a8a1bdb63b9c61098160a8fd061d..aa4fd4e1ebe71b0eabf0eb826c50fe81119de8ea 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.156 2010/01/02 16:57:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.157 2010/01/15 22:36:32 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1905,14 +1905,15 @@ record_plan_function_dependency(PlannerGlobal *glob, Oid funcid)
 
 /*
  * extract_query_dependencies
- *		Given a list of not-yet-planned queries (i.e. Query nodes),
- *		extract their dependencies just as set_plan_references would do.
+ *		Given a not-yet-planned query or queries (i.e. a Query node or list
+ *		of Query nodes), extract dependencies just as set_plan_references
+ *		would do.
  *
  * This is needed by plancache.c to handle invalidation of cached unplanned
  * queries.
  */
 void
-extract_query_dependencies(List *queries,
+extract_query_dependencies(Node *query,
 						   List **relationOids,
 						   List **invalItems)
 {
@@ -1924,7 +1925,7 @@ extract_query_dependencies(List *queries,
 	glob.relationOids = NIL;
 	glob.invalItems = NIL;
 
-	(void) extract_query_dependencies_walker((Node *) queries, &glob);
+	(void) extract_query_dependencies_walker(query, &glob);
 
 	*relationOids = glob.relationOids;
 	*invalItems = glob.invalItems;
@@ -1943,6 +1944,19 @@ extract_query_dependencies_walker(Node *node, PlannerGlobal *context)
 		Query	   *query = (Query *) node;
 		ListCell   *lc;
 
+		if (query->commandType == CMD_UTILITY)
+		{
+			/* Ignore utility statements, except EXPLAIN */
+			if (IsA(query->utilityStmt, ExplainStmt))
+			{
+				query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
+				Assert(IsA(query, Query));
+				Assert(query->commandType != CMD_UTILITY);
+			}
+			else
+				return false;
+		}
+
 		/* Collect relation OIDs in this Query's rtable */
 		foreach(lc, query->rtable)
 		{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a0e565b187cad832f11e80437a5618c4fba919fe..efa4e47b1a46fb7972ac6f482dec9a1419df7df9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -17,7 +17,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.399 2010/01/02 16:57:48 momjian Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.400 2010/01/15 22:36:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -257,16 +257,12 @@ analyze_requires_snapshot(Node *parseTree)
 			break;
 
 		case T_ExplainStmt:
-
-			/*
-			 * We only need a snapshot in varparams case, but it doesn't seem
-			 * worth complicating this function's API to distinguish that.
-			 */
+			/* yes, because we must analyze the contained statement */
 			result = true;
 			break;
 
 		default:
-			/* utility statements don't have any active parse analysis */
+			/* other utility statements don't have any real parse analysis */
 			result = false;
 			break;
 	}
@@ -1993,29 +1989,21 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
  * transformExplainStmt -
  *	transform an EXPLAIN Statement
  *
- * EXPLAIN is just like other utility statements in that we emit it as a
- * CMD_UTILITY Query node with no transformation of the raw parse tree.
- * However, if p_coerce_param_hook is set, it could be that the client is
- * expecting us to resolve parameter types in something like
- *		EXPLAIN SELECT * FROM tab WHERE col = $1
- * To deal with such cases, we run parse analysis and throw away the result;
- * this is a bit grotty but not worth contorting the rest of the system for.
- * (The approach we use for DECLARE CURSOR won't work because the statement
- * being explained isn't necessarily a SELECT, and in particular might rewrite
- * to multiple parsetrees.)
+ * EXPLAIN is like other utility statements in that we emit it as a
+ * CMD_UTILITY Query node; however, we must first transform the contained
+ * query.  We used to postpone that until execution, but it's really necessary
+ * to do it during the normal parse analysis phase to ensure that side effects
+ * of parser hooks happen at the expected time.
  */
 static Query *
 transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
 {
 	Query	   *result;
 
-	if (pstate->p_coerce_param_hook != NULL)
-	{
-		/* Since parse analysis scribbles on its input, copy the tree first! */
-		(void) transformStmt(pstate, copyObject(stmt->query));
-	}
+	/* transform contained query */
+	stmt->query = (Node *) transformStmt(pstate, stmt->query);
 
-	/* Now return the untransformed command as a utility Query */
+	/* represent the command as a utility Query */
 	result = makeNode(Query);
 	result->commandType = CMD_UTILITY;
 	result->utilityStmt = (Node *) stmt;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index acacbec094a2f1216453b7ba4377175f6958231e..96b0aa735f40f2e8673195b1728f359704b7b657 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.328 2010/01/06 03:04:01 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.329 2010/01/15 22:36:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2438,6 +2438,7 @@ GetCommandLogLevel(Node *parsetree)
 
 					if (strcmp(opt->defname, "analyze") == 0)
 						analyze = defGetBoolean(opt);
+					/* don't "break", as explain.c will use the last value */
 				}
 				if (analyze)
 					return GetCommandLogLevel(stmt->query);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index ffa117b66cd2cf2a9fb787392711ef1b3700e010..114cd9b9756c2e9282d244b89948a395e50b9d0b 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.33 2010/01/13 16:56:56 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.34 2010/01/15 22:36:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -359,13 +359,27 @@ StoreCachedPlan(CachedPlanSource *plansource,
 	plan->context = plan_context;
 	if (plansource->fully_planned)
 	{
-		/* Planner already extracted dependencies, we don't have to */
+		/*
+		 * Planner already extracted dependencies, we don't have to ...
+		 * except in the case of EXPLAIN.  We assume here that EXPLAIN
+		 * can't appear in a list with other commands.
+		 */
 		plan->relationOids = plan->invalItems = NIL;
+
+		if (list_length(stmt_list) == 1 &&
+			IsA(linitial(stmt_list), ExplainStmt))
+		{
+			ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
+
+			extract_query_dependencies(estmt->query,
+									   &plan->relationOids,
+									   &plan->invalItems);
+		}
 	}
 	else
 	{
 		/* Use the planner machinery to extract dependencies */
-		extract_query_dependencies(stmt_list,
+		extract_query_dependencies((Node *) stmt_list,
 								   &plan->relationOids,
 								   &plan->invalItems);
 	}
@@ -685,7 +699,24 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
 
 		Assert(!IsA(plannedstmt, Query));
 		if (!IsA(plannedstmt, PlannedStmt))
-			continue;			/* Ignore utility statements */
+		{
+			/*
+			 * Ignore utility statements, except EXPLAIN which contains a
+			 * parsed-but-not-planned query.  Note: it's okay to use
+			 * ScanQueryForLocks, even though the query hasn't been through
+			 * rule rewriting, because rewriting doesn't change the query
+			 * representation.
+			 */
+			if (IsA(plannedstmt, ExplainStmt))
+			{
+				Query	   *query;
+
+				query = (Query *) ((ExplainStmt *) plannedstmt)->query;
+				Assert(IsA(query, Query));
+				ScanQueryForLocks(query, acquire);
+			}
+			continue;
+		}
 
 		rt_index = 0;
 		foreach(lc2, plannedstmt->rtable)
@@ -739,6 +770,19 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
 		Query	   *query = (Query *) lfirst(lc);
 
 		Assert(IsA(query, Query));
+
+		if (query->commandType == CMD_UTILITY)
+		{
+			/* Ignore utility statements, except EXPLAIN */
+			if (IsA(query->utilityStmt, ExplainStmt))
+			{
+				query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
+				Assert(IsA(query, Query));
+				ScanQueryForLocks(query, acquire);
+			}
+			continue;
+		}
+
 		ScanQueryForLocks(query, acquire);
 	}
 }
@@ -752,6 +796,9 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
 	ListCell   *lc;
 	int			rt_index;
 
+	/* Shouldn't get called on utility commands */
+	Assert(parsetree->commandType != CMD_UTILITY);
+
 	/*
 	 * First, process RTEs of the current query level.
 	 */
@@ -942,7 +989,16 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 		/* No work if it's already invalidated */
 		if (!plan || plan->dead)
 			continue;
-		if (plan->fully_planned)
+
+		/*
+		 * Check the list we built ourselves; this covers unplanned cases
+		 * including EXPLAIN.
+		 */
+		if ((relid == InvalidOid) ? plan->relationOids != NIL :
+			list_member_oid(plan->relationOids, relid))
+			plan->dead = true;
+
+		if (plan->fully_planned && !plan->dead)
 		{
 			/* Have to check the per-PlannedStmt relid lists */
 			ListCell   *lc2;
@@ -963,13 +1019,6 @@ PlanCacheRelCallback(Datum arg, Oid relid)
 				}
 			}
 		}
-		else
-		{
-			/* Otherwise check the single list we built ourselves */
-			if ((relid == InvalidOid) ? plan->relationOids != NIL :
-				list_member_oid(plan->relationOids, relid))
-				plan->dead = true;
-		}
 	}
 }
 
@@ -992,15 +1041,34 @@ PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
 	{
 		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
 		CachedPlan *plan = plansource->plan;
+		ListCell   *lc2;
 
 		/* No work if it's already invalidated */
 		if (!plan || plan->dead)
 			continue;
-		if (plan->fully_planned)
+
+		/*
+		 * Check the list we built ourselves; this covers unplanned cases
+		 * including EXPLAIN.
+		 */
+		foreach(lc2, plan->invalItems)
 		{
-			/* Have to check the per-PlannedStmt inval-item lists */
-			ListCell   *lc2;
+			PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
 
+			if (item->cacheId != cacheid)
+				continue;
+			if (tuplePtr == NULL ||
+				ItemPointerEquals(tuplePtr, &item->tupleId))
+			{
+				/* Invalidate the plan! */
+				plan->dead = true;
+				break;
+			}
+		}
+
+		if (plan->fully_planned && !plan->dead)
+		{
+			/* Have to check the per-PlannedStmt inval-item lists */
 			foreach(lc2, plan->stmt_list)
 			{
 				PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
@@ -1027,26 +1095,6 @@ PlanCacheFuncCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
 					break;		/* out of stmt_list scan */
 			}
 		}
-		else
-		{
-			/* Otherwise check the single list we built ourselves */
-			ListCell   *lc2;
-
-			foreach(lc2, plan->invalItems)
-			{
-				PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2);
-
-				if (item->cacheId != cacheid)
-					continue;
-				if (tuplePtr == NULL ||
-					ItemPointerEquals(tuplePtr, &item->tupleId))
-				{
-					/* Invalidate the plan! */
-					plan->dead = true;
-					break;
-				}
-			}
-		}
 	}
 }
 
@@ -1086,7 +1134,9 @@ ResetPlanCache(void)
 		 * 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.
+		 * contains at least one non-utility statement.  (EXPLAIN counts as
+		 * a non-utility statement, though, since it contains an analyzed
+		 * query that might have dependencies.)
 		 */
 		if (plan->fully_planned)
 		{
@@ -1096,7 +1146,8 @@ ResetPlanCache(void)
 				PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
 
 				Assert(!IsA(plannedstmt, Query));
-				if (IsA(plannedstmt, PlannedStmt))
+				if (IsA(plannedstmt, PlannedStmt) ||
+					IsA(plannedstmt, ExplainStmt))
 				{
 					/* non-utility statement, so invalidate */
 					plan->dead = true;
@@ -1112,7 +1163,8 @@ ResetPlanCache(void)
 				Query	   *query = (Query *) lfirst(lc2);
 
 				Assert(IsA(query, Query));
-				if (query->commandType != CMD_UTILITY)
+				if (query->commandType != CMD_UTILITY ||
+					IsA(query->utilityStmt, ExplainStmt))
 				{
 					/* non-utility statement, so invalidate */
 					plan->dead = true;
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 93b2c03cee9abd4b00eee0af52fe4a9bf79f4a99..12ef269e610608a7fea7b7a49c523423de321e4e 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.40 2010/01/02 16:58:04 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.41 2010/01/15 22:36:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -103,7 +103,4 @@ typedef struct ParamExecData
 /* Functions found in src/backend/nodes/params.c */
 extern ParamListInfo copyParamList(ParamListInfo from);
 
-extern void setupParserWithParamList(struct ParseState *pstate,
-									 ParamListInfo params);
-
 #endif   /* PARAMS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9a0bb8eec3f8a734f46e7fc1390f0d369e2b47ac..a03597c9f375fa4c093d4e0c188ffabb364ecf19 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.423 2010/01/06 05:31:14 itagaki Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.424 2010/01/15 22:36:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2260,12 +2260,16 @@ typedef struct VacuumStmt
 
 /* ----------------------
  *		Explain Statement
+ *
+ * The "query" field is either a raw parse tree (SelectStmt, InsertStmt, etc)
+ * or a Query node if parse analysis has been done.  Note that rewriting and
+ * planning of the query are always postponed until execution of EXPLAIN.
  * ----------------------
  */
 typedef struct ExplainStmt
 {
 	NodeTag		type;
-	Node	   *query;			/* the query (as a raw parse tree) */
+	Node	   *query;			/* the query (see comments above) */
 	List	   *options;		/* list of DefElem nodes */
 } ExplainStmt;
 
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 7e59cd4a6bef4eac37998f4625182c42c18e3c2c..024142250ac3e0c1b2cd3515fd83793908e0bc12 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.123 2010/01/02 16:58:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.124 2010/01/15 22:36:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -122,7 +122,7 @@ extern void fix_opfuncids(Node *node);
 extern void set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerGlobal *glob, Oid funcid);
-extern void extract_query_dependencies(List *queries,
+extern void extract_query_dependencies(Node *query,
 						   List **relationOids,
 						   List **invalItems);