From 9cbd0c155d1602aad879f510256b626c58942080 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 20 Feb 2007 17:32:18 +0000
Subject: [PATCH] Remove the Query structure from the executor's API.  This
 allows us to stop storing mostly-redundant Query trees in prepared
 statements, portals, etc. To replace Query, a new node type called
 PlannedStmt is inserted by the planner at the top of a completed plan tree;
 this carries just the fields of Query that are still needed at runtime.  The
 statement lists kept in portals etc. now consist of intermixed PlannedStmt
 and bare utility-statement nodes --- no Query.  This incidentally allows us
 to remove some fields from Query and Plan nodes that shouldn't have been
 there in the first place.

Still to do: simplify the execution-time range table; at the moment the
range table passed to the executor still contains Query trees for subqueries.

initdb forced due to change of stored rules.
---
 src/backend/commands/copy.c           |   9 +-
 src/backend/commands/explain.c        |  13 +-
 src/backend/commands/portalcmds.c     |  10 +-
 src/backend/commands/prepare.c        | 186 +++++++-------
 src/backend/executor/execMain.c       | 154 ++++++------
 src/backend/executor/execUtils.c      |   4 +-
 src/backend/executor/functions.c      |  50 ++--
 src/backend/executor/spi.c            | 149 +++++-------
 src/backend/nodes/copyfuncs.c         |  59 +++--
 src/backend/nodes/equalfuncs.c        |  29 ++-
 src/backend/nodes/outfuncs.c          |  48 +++-
 src/backend/nodes/readfuncs.c         |  23 +-
 src/backend/optimizer/path/allpaths.c |   8 +-
 src/backend/optimizer/plan/planagg.c  |   5 +-
 src/backend/optimizer/plan/planner.c  |  66 +++--
 src/backend/parser/analyze.c          |  25 +-
 src/backend/parser/gram.y             |  59 +++--
 src/backend/tcop/postgres.c           | 150 ++++++------
 src/backend/tcop/pquery.c             | 338 +++++++++++++++++---------
 src/backend/tcop/utility.c            | 310 +++++++++++++----------
 src/backend/utils/mmgr/portalmem.c    |  53 ++--
 src/include/catalog/catversion.h      |   4 +-
 src/include/commands/portalcmds.h     |   3 +-
 src/include/commands/prepare.h        |  16 +-
 src/include/executor/execdesc.h       |  21 +-
 src/include/executor/executor.h       |   3 +-
 src/include/executor/spi_priv.h       |  11 +-
 src/include/nodes/execnodes.h         |   4 +-
 src/include/nodes/nodes.h             |  20 +-
 src/include/nodes/parsenodes.h        |  62 +----
 src/include/nodes/plannodes.h         |  53 +++-
 src/include/nodes/primnodes.h         |  25 +-
 src/include/nodes/relation.h          |   6 +-
 src/include/optimizer/planner.h       |   6 +-
 src/include/tcop/pquery.h             |   7 +-
 src/include/tcop/tcopprot.h           |   5 +-
 src/include/tcop/utility.h            |  10 +-
 src/include/utils/portal.h            |  16 +-
 src/pl/plpgsql/src/pl_exec.c          |  29 ++-
 39 files changed, 1162 insertions(+), 887 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e61384beec8..30118d5237b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.275 2007/01/25 02:17:26 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.276 2007/02/20 17:32:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -986,10 +986,11 @@ DoCopy(const CopyStmt *stmt)
 	{
 		Query	   *query = stmt->query;
 		List	   *rewritten;
-		Plan	   *plan;
+		PlannedStmt *plan;
 		DestReceiver *dest;
 
 		Assert(query);
+		Assert(query->commandType == CMD_SELECT);
 		Assert(!is_from);
 		cstate->rel = NULL;
 
@@ -999,6 +1000,7 @@ DoCopy(const CopyStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("COPY (SELECT) WITH OIDS is not supported")));
 
+		/* Query mustn't use INTO, either */
 		if (query->into)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1016,7 +1018,6 @@ DoCopy(const CopyStmt *stmt)
 		 * shouldn't modify its input ... FIXME someday.
 		 */
 		query = copyObject(query);
-		Assert(query->commandType == CMD_SELECT);
 
 		/*
 		 * Must acquire locks in case we didn't come fresh from the parser.
@@ -1051,7 +1052,7 @@ DoCopy(const CopyStmt *stmt)
 		((DR_copy *) dest)->cstate = cstate;
 
 		/* Create a QueryDesc requesting no output */
-		cstate->queryDesc = CreateQueryDesc(query, plan,
+		cstate->queryDesc = CreateQueryDesc(plan,
 											ActiveSnapshot, InvalidSnapshot,
 											dest, NULL, false);
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7b2c521a353..58b7e6ded9a 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.155 2007/02/19 02:23:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.156 2007/02/20 17:32:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -149,7 +149,7 @@ static void
 ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
 				TupOutputState *tstate)
 {
-	Plan	   *plan;
+	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
 	bool		isCursor = false;
 	int			cursorOptions = 0;
@@ -203,7 +203,7 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
 	ActiveSnapshot->curcid = GetCurrentCommandId();
 
 	/* Create a QueryDesc requesting no output */
-	queryDesc = CreateQueryDesc(query, plan,
+	queryDesc = CreateQueryDesc(plan,
 								ActiveSnapshot, InvalidSnapshot,
 								None_Receiver, params,
 								stmt->analyze);
@@ -260,14 +260,14 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 
 	es->printNodes = stmt->verbose;
 	es->printAnalyze = stmt->analyze;
-	es->rtable = queryDesc->parsetree->rtable;
+	es->rtable = queryDesc->plannedstmt->rtable;
 
 	if (es->printNodes)
 	{
 		char	   *s;
 		char	   *f;
 
-		s = nodeToString(queryDesc->plantree);
+		s = nodeToString(queryDesc->plannedstmt->planTree);
 		if (s)
 		{
 			if (Explain_pretty_print)
@@ -282,7 +282,8 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 	}
 
 	initStringInfo(&buf);
-	explain_outNode(&buf, queryDesc->plantree, queryDesc->planstate,
+	explain_outNode(&buf,
+					queryDesc->plannedstmt->planTree, queryDesc->planstate,
 					NULL, 0, es);
 
 	/*
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index d1c119ca08f..0219650c069 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.60 2007/02/06 22:49:24 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.61 2007/02/20 17:32:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,7 +42,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
 {
 	List	   *rewritten;
 	Query	   *query;
-	Plan	   *plan;
+	PlannedStmt *plan;
 	Portal		portal;
 	MemoryContext oldContext;
 
@@ -98,13 +98,12 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
 	plan = planner(query, true, stmt->options, params);
 
 	/*
-	 * Create a portal and copy the query and plan into its memory context.
+	 * Create a portal and copy the plan into its memory context.
 	 */
 	portal = CreatePortal(stmt->portalname, false, false);
 
 	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
-	query = copyObject(query);
 	plan = copyObject(plan);
 
 	/*
@@ -115,7 +114,6 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
 					  NULL,
 					  debug_query_string ? pstrdup(debug_query_string) : NULL,
 					  "SELECT", /* cursor's query is always a SELECT */
-					  list_make1(query),
 					  list_make1(plan),
 					  PortalGetHeapMemory(portal));
 
@@ -140,7 +138,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
 	portal->cursorOptions = stmt->options;
 	if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
 	{
-		if (ExecSupportsBackwardScan(plan))
+		if (ExecSupportsBackwardScan(plan->planTree))
 			portal->cursorOptions |= CURSOR_OPT_SCROLL;
 		else
 			portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index ecd5074211e..8a5382c7378 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
  * Copyright (c) 2002-2007, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.68 2007/01/28 19:05:35 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.69 2007/02/20 17:32:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -114,9 +114,9 @@ PrepareQuery(PrepareStmt *stmt)
 	StorePreparedStatement(stmt->name,
 						   debug_query_string,
 						   commandTag,
-						   query_list,
 						   plan_list,
 						   stmt->argtype_oids,
+						   true,
 						   true);
 }
 
@@ -129,8 +129,7 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
 {
 	PreparedStatement *entry;
 	char	   *query_string;
-	List	   *query_list,
-			   *plan_list;
+	List	   *plan_list;
 	MemoryContext qcontext;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
@@ -139,13 +138,18 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
+	/*
+	 * Punt if not fully planned.  (Currently, that only happens for the
+	 * protocol-level unnamed statement, which can't be accessed from SQL;
+	 * so there's no point in doing more than a quick check here.)
+	 */
+	if (!entry->fully_planned)
+		elog(ERROR, "EXECUTE does not support unplanned prepared statements");
+
 	query_string = entry->query_string;
-	query_list = entry->query_list;
-	plan_list = entry->plan_list;
+	plan_list = entry->stmt_list;
 	qcontext = entry->context;
 
-	Assert(list_length(query_list) == list_length(plan_list));
-
 	/* Evaluate parameters, if any */
 	if (entry->argtype_list != NIL)
 	{
@@ -172,30 +176,26 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
 	if (stmt->into)
 	{
 		MemoryContext oldContext;
-		Query	   *query;
+		PlannedStmt *pstmt;
 
-		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+		qcontext = PortalGetHeapMemory(portal);
+		oldContext = MemoryContextSwitchTo(qcontext);
 
 		if (query_string)
 			query_string = pstrdup(query_string);
-		query_list = copyObject(query_list);
 		plan_list = copyObject(plan_list);
-		qcontext = PortalGetHeapMemory(portal);
 
-		if (list_length(query_list) != 1)
+		if (list_length(plan_list) != 1)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("prepared statement is not a SELECT")));
-		query = (Query *) linitial(query_list);
-		if (query->commandType != CMD_SELECT)
+		pstmt = (PlannedStmt *) linitial(plan_list);
+		if (!IsA(pstmt, PlannedStmt) ||
+			pstmt->commandType != CMD_SELECT)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("prepared statement is not a SELECT")));
-		query->into = copyObject(stmt->into);
-		query->intoOptions = copyObject(stmt->intoOptions);
-		query->intoOnCommit = stmt->into_on_commit;
-		if (stmt->into_tbl_space)
-			query->intoTableSpaceName = pstrdup(stmt->into_tbl_space);
+		pstmt->into = copyObject(stmt->into);
 
 		MemoryContextSwitchTo(oldContext);
 	}
@@ -204,7 +204,6 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
 					  NULL,
 					  query_string,
 					  entry->commandTag,
-					  query_list,
 					  plan_list,
 					  qcontext);
 
@@ -305,9 +304,9 @@ void
 StorePreparedStatement(const char *stmt_name,
 					   const char *query_string,
 					   const char *commandTag,
-					   List *query_list,
-					   List *plan_list,
+					   List *stmt_list,
 					   List *argtype_list,
+					   bool fully_planned,
 					   bool from_sql)
 {
 	PreparedStatement *entry;
@@ -345,8 +344,7 @@ StorePreparedStatement(const char *stmt_name,
 	 * incomplete (ie corrupt) hashtable entry.
 	 */
 	qstring = query_string ? pstrdup(query_string) : NULL;
-	query_list = (List *) copyObject(query_list);
-	plan_list = (List *) copyObject(plan_list);
+	stmt_list = (List *) copyObject(stmt_list);
 	argtype_list = list_copy(argtype_list);
 
 	/* Now we can add entry to hash table */
@@ -363,12 +361,12 @@ StorePreparedStatement(const char *stmt_name,
 	/* Fill in the hash table entry with copied data */
 	entry->query_string = qstring;
 	entry->commandTag = commandTag;
-	entry->query_list = query_list;
-	entry->plan_list = plan_list;
+	entry->stmt_list = stmt_list;
 	entry->argtype_list = argtype_list;
+	entry->fully_planned = fully_planned;
+	entry->from_sql = from_sql;
 	entry->context = entrycxt;
 	entry->prepare_time = GetCurrentStatementStartTimestamp();
-	entry->from_sql = from_sql;
 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -426,21 +424,54 @@ FetchPreparedStatementParams(const char *stmt_name)
 TupleDesc
 FetchPreparedStatementResultDesc(PreparedStatement *stmt)
 {
+	Node	   *node;
 	Query	   *query;
+	PlannedStmt *pstmt;
 
-	switch (ChoosePortalStrategy(stmt->query_list))
+	switch (ChoosePortalStrategy(stmt->stmt_list))
 	{
 		case PORTAL_ONE_SELECT:
-			query = (Query *) linitial(stmt->query_list);
-			return ExecCleanTypeFromTL(query->targetList, false);
+			node = (Node *) linitial(stmt->stmt_list);
+			if (IsA(node, Query))
+			{
+				query = (Query *) node;
+				return ExecCleanTypeFromTL(query->targetList, false);
+			}
+			if (IsA(node, PlannedStmt))
+			{
+				pstmt = (PlannedStmt *) node;
+				return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
+			}
+			/* other cases shouldn't happen, but return NULL */
+			break;
 
 		case PORTAL_ONE_RETURNING:
-			query = PortalListGetPrimaryQuery(stmt->query_list);
-			return ExecCleanTypeFromTL(query->returningList, false);
+			node = PortalListGetPrimaryStmt(stmt->stmt_list);
+			if (IsA(node, Query))
+			{
+				query = (Query *) node;
+				Assert(query->returningList);
+				return ExecCleanTypeFromTL(query->returningList, false);
+			}
+			if (IsA(node, PlannedStmt))
+			{
+				pstmt = (PlannedStmt *) node;
+				Assert(pstmt->returningLists);
+				return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false);
+			}
+			/* other cases shouldn't happen, but return NULL */
+			break;
 
 		case PORTAL_UTIL_SELECT:
-			query = (Query *) linitial(stmt->query_list);
-			return UtilityTupleDescriptor(query->utilityStmt);
+			node = (Node *) linitial(stmt->stmt_list);
+			if (IsA(node, Query))
+			{
+				query = (Query *) node;
+				Assert(query->utilityStmt);
+				return UtilityTupleDescriptor(query->utilityStmt);
+			}
+			/* else it's a bare utility statement */
+			return UtilityTupleDescriptor(node);
 
 		case PORTAL_MULTI_QUERY:
 			/* will not return tuples */
@@ -460,7 +491,7 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt)
 bool
 PreparedStatementReturnsTuples(PreparedStatement *stmt)
 {
-	switch (ChoosePortalStrategy(stmt->query_list))
+	switch (ChoosePortalStrategy(stmt->stmt_list))
 	{
 		case PORTAL_ONE_SELECT:
 		case PORTAL_ONE_RETURNING:
@@ -480,52 +511,15 @@ PreparedStatementReturnsTuples(PreparedStatement *stmt)
  * targetlist.
  *
  * Note: do not modify the result.
- *
- * XXX be careful to keep this in sync with FetchPortalTargetList,
- * and with UtilityReturnsTuples.
  */
 List *
 FetchPreparedStatementTargetList(PreparedStatement *stmt)
 {
-	PortalStrategy strategy = ChoosePortalStrategy(stmt->query_list);
-
-	if (strategy == PORTAL_ONE_SELECT)
-		return ((Query *) linitial(stmt->query_list))->targetList;
-	if (strategy == PORTAL_ONE_RETURNING)
-		return (PortalListGetPrimaryQuery(stmt->query_list))->returningList;
-	if (strategy == PORTAL_UTIL_SELECT)
-	{
-		Node	   *utilityStmt;
-
-		utilityStmt = ((Query *) linitial(stmt->query_list))->utilityStmt;
-		switch (nodeTag(utilityStmt))
-		{
-			case T_FetchStmt:
-				{
-					FetchStmt  *substmt = (FetchStmt *) utilityStmt;
-					Portal		subportal;
-
-					Assert(!substmt->ismove);
-					subportal = GetPortalByName(substmt->portalname);
-					Assert(PortalIsValid(subportal));
-					return FetchPortalTargetList(subportal);
-				}
-
-			case T_ExecuteStmt:
-				{
-					ExecuteStmt *substmt = (ExecuteStmt *) utilityStmt;
-					PreparedStatement *entry;
-
-					Assert(!substmt->into);
-					entry = FetchPreparedStatement(substmt->name, true);
-					return FetchPreparedStatementTargetList(entry);
-				}
-
-			default:
-				break;
-		}
-	}
-	return NIL;
+	/* no point in looking if it doesn't return tuples */
+	if (ChoosePortalStrategy(stmt->stmt_list) == PORTAL_MULTI_QUERY)
+		return NIL;
+	/* get the primary statement and find out what it returns */
+	return FetchStatementTargetList(PortalListGetPrimaryStmt(stmt->stmt_list));
 }
 
 /*
@@ -574,10 +568,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 {
 	ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
 	PreparedStatement *entry;
-	ListCell   *q,
-			   *p;
-	List	   *query_list,
-			   *plan_list;
+	List	   *plan_list;
+	ListCell   *p;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
 
@@ -587,10 +579,15 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(execstmt->name, true);
 
-	query_list = entry->query_list;
-	plan_list = entry->plan_list;
+	/*
+	 * Punt if not fully planned.  (Currently, that only happens for the
+	 * protocol-level unnamed statement, which can't be accessed from SQL;
+	 * so there's no point in doing more than a quick check here.)
+	 */
+	if (!entry->fully_planned)
+		elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements");
 
-	Assert(list_length(query_list) == list_length(plan_list));
+	plan_list = entry->stmt_list;
 
 	/* Evaluate parameters, if any */
 	if (entry->argtype_list != NIL)
@@ -606,17 +603,16 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 	}
 
 	/* Explain each query */
-	forboth(q, query_list, p, plan_list)
+	foreach(p, plan_list)
 	{
-		Query	   *query = (Query *) lfirst(q);
-		Plan	   *plan = (Plan *) lfirst(p);
+		PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
 		bool		is_last_query;
 
 		is_last_query = (lnext(p) == NULL);
 
-		if (query->commandType == CMD_UTILITY)
+		if (!IsA(pstmt, PlannedStmt))
 		{
-			if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
+			if (IsA(pstmt, NotifyStmt))
 				do_text_output_oneline(tstate, "NOTIFY");
 			else
 				do_text_output_oneline(tstate, "UTILITY");
@@ -627,15 +623,15 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 
 			if (execstmt->into)
 			{
-				if (query->commandType != CMD_SELECT)
+				if (pstmt->commandType != CMD_SELECT)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("prepared statement is not a SELECT")));
 
-				/* Copy the query so we can modify it */
-				query = copyObject(query);
+				/* Copy the stmt so we can modify it */
+				pstmt = copyObject(pstmt);
 
-				query->into = execstmt->into;
+				pstmt->into = execstmt->into;
 			}
 
 			/*
@@ -648,7 +644,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 			ActiveSnapshot->curcid = GetCurrentCommandId();
 
 			/* Create a QueryDesc requesting no output */
-			qdesc = CreateQueryDesc(query, plan,
+			qdesc = CreateQueryDesc(pstmt,
 									ActiveSnapshot, InvalidSnapshot,
 									None_Receiver,
 									paramLI, stmt->analyze);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 91a76bb0779..405b58f9fd6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.286 2007/02/02 00:07:02 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.287 2007/02/20 17:32:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -93,7 +93,8 @@ static void ExecProcessReturning(ProjectionInfo *projectReturning,
 static TupleTableSlot *EvalPlanQualNext(EState *estate);
 static void EndEvalPlanQual(EState *estate);
 static void ExecCheckRTEPerms(RangeTblEntry *rte);
-static void ExecCheckXactReadOnly(Query *parsetree);
+static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
+static void ExecCheckRangeTblReadOnly(List *rtable);
 static void EvalPlanQualStart(evalPlanQual *epq, EState *estate,
 				  evalPlanQual *priorepq);
 static void EvalPlanQualStop(evalPlanQual *epq);
@@ -139,7 +140,7 @@ ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 * planned to non-temporary tables.  EXPLAIN is considered read-only.
 	 */
 	if (XactReadOnly && !(eflags & EXEC_FLAG_EXPLAIN_ONLY))
-		ExecCheckXactReadOnly(queryDesc->parsetree);
+		ExecCheckXactReadOnly(queryDesc->plannedstmt);
 
 	/*
 	 * Build EState, switch into per-query memory context for startup.
@@ -154,9 +155,9 @@ ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	estate->es_param_list_info = queryDesc->params;
 
-	if (queryDesc->plantree->nParamExec > 0)
+	if (queryDesc->plannedstmt->nParamExec > 0)
 		estate->es_param_exec_vals = (ParamExecData *)
-			palloc0(queryDesc->plantree->nParamExec * sizeof(ParamExecData));
+			palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData));
 
 	/*
 	 * Copy other important information into the EState
@@ -227,7 +228,7 @@ ExecutorRun(QueryDesc *queryDesc,
 	estate->es_lastoid = InvalidOid;
 
 	sendTuples = (operation == CMD_SELECT ||
-				  queryDesc->parsetree->returningList);
+				  queryDesc->plannedstmt->returningLists);
 
 	if (sendTuples)
 		(*dest->rStartup) (dest, operation, queryDesc->tupDesc);
@@ -414,26 +415,41 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
  * Check that the query does not imply any writes to non-temp tables.
  */
 static void
-ExecCheckXactReadOnly(Query *parsetree)
+ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 {
-	ListCell   *l;
-
 	/*
 	 * CREATE TABLE AS or SELECT INTO?
 	 *
 	 * XXX should we allow this if the destination is temp?
 	 */
-	if (parsetree->into != NULL)
+	if (plannedstmt->into != NULL)
 		goto fail;
 
 	/* Fail if write permissions are requested on any non-temp table */
-	foreach(l, parsetree->rtable)
+	ExecCheckRangeTblReadOnly(plannedstmt->rtable);
+
+	return;
+
+fail:
+	ereport(ERROR,
+			(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+			 errmsg("transaction is read-only")));
+}
+
+static void
+ExecCheckRangeTblReadOnly(List *rtable)
+{
+	ListCell   *l;
+
+	/* Fail if write permissions are requested on any non-temp table */
+	foreach(l, rtable)
 	{
 		RangeTblEntry *rte = lfirst(l);
 
 		if (rte->rtekind == RTE_SUBQUERY)
 		{
-			ExecCheckXactReadOnly(rte->subquery);
+			Assert(!rte->subquery->into);
+			ExecCheckRangeTblReadOnly(rte->subquery->rtable);
 			continue;
 		}
 
@@ -469,11 +485,11 @@ static void
 InitPlan(QueryDesc *queryDesc, int eflags)
 {
 	CmdType		operation = queryDesc->operation;
-	Query	   *parseTree = queryDesc->parsetree;
-	Plan	   *plan = queryDesc->plantree;
+	PlannedStmt *plannedstmt = queryDesc->plannedstmt;
+	Plan	   *plan = plannedstmt->planTree;
+	List	   *rangeTable = plannedstmt->rtable;
 	EState	   *estate = queryDesc->estate;
 	PlanState  *planstate;
-	List	   *rangeTable;
 	TupleDesc	tupType;
 	ListCell   *l;
 
@@ -482,12 +498,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	 * rangetable here --- subplan RTEs will be checked during
 	 * ExecInitSubPlan().
 	 */
-	ExecCheckRTPerms(parseTree->rtable);
-
-	/*
-	 * get information from query descriptor
-	 */
-	rangeTable = parseTree->rtable;
+	ExecCheckRTPerms(rangeTable);
 
 	/*
 	 * initialize the node's execution state
@@ -495,50 +506,27 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	estate->es_range_table = rangeTable;
 
 	/*
-	 * if there is a result relation, initialize result relation stuff
+	 * initialize result relation stuff
 	 */
-	if (parseTree->resultRelation)
+	if (plannedstmt->resultRelations)
 	{
-		List	   *resultRelations = parseTree->resultRelations;
-		int			numResultRelations;
+		List	   *resultRelations = plannedstmt->resultRelations;
+		int			numResultRelations = list_length(resultRelations);
 		ResultRelInfo *resultRelInfos;
+		ResultRelInfo *resultRelInfo;
 
-		if (resultRelations != NIL)
-		{
-			/*
-			 * Multiple result relations (due to inheritance)
-			 * parseTree->resultRelations identifies them all
-			 */
-			ResultRelInfo *resultRelInfo;
-
-			numResultRelations = list_length(resultRelations);
-			resultRelInfos = (ResultRelInfo *)
-				palloc(numResultRelations * sizeof(ResultRelInfo));
-			resultRelInfo = resultRelInfos;
-			foreach(l, resultRelations)
-			{
-				initResultRelInfo(resultRelInfo,
-								  lfirst_int(l),
-								  rangeTable,
-								  operation,
-								  estate->es_instrument);
-				resultRelInfo++;
-			}
-		}
-		else
+		resultRelInfos = (ResultRelInfo *)
+			palloc(numResultRelations * sizeof(ResultRelInfo));
+		resultRelInfo = resultRelInfos;
+		foreach(l, resultRelations)
 		{
-			/*
-			 * Single result relation identified by parseTree->resultRelation
-			 */
-			numResultRelations = 1;
-			resultRelInfos = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
-			initResultRelInfo(resultRelInfos,
-							  parseTree->resultRelation,
+			initResultRelInfo(resultRelInfo,
+							  lfirst_int(l),
 							  rangeTable,
 							  operation,
 							  estate->es_instrument);
+			resultRelInfo++;
 		}
-
 		estate->es_result_relations = resultRelInfos;
 		estate->es_num_result_relations = numResultRelations;
 		/* Initialize to first or only result rel */
@@ -560,10 +548,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	 * correct tuple descriptors.  (Other SELECT INTO stuff comes later.)
 	 */
 	estate->es_select_into = false;
-	if (operation == CMD_SELECT && parseTree->into != NULL)
+	if (operation == CMD_SELECT && plannedstmt->into != NULL)
 	{
 		estate->es_select_into = true;
-		estate->es_into_oids = interpretOidsOption(parseTree->intoOptions);
+		estate->es_into_oids = interpretOidsOption(plannedstmt->into->options);
 	}
 
 	/*
@@ -572,7 +560,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	 * While we are at it, build the ExecRowMark list.
 	 */
 	estate->es_rowMarks = NIL;
-	foreach(l, parseTree->rowMarks)
+	foreach(l, plannedstmt->rowMarks)
 	{
 		RowMarkClause *rc = (RowMarkClause *) lfirst(l);
 		Oid			relid = getrelid(rc->rti, rangeTable);
@@ -600,13 +588,13 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	{
 		int			nSlots = ExecCountSlotsNode(plan);
 
-		if (parseTree->resultRelations != NIL)
-			nSlots += list_length(parseTree->resultRelations);
+		if (plannedstmt->resultRelations != NIL)
+			nSlots += list_length(plannedstmt->resultRelations);
 		else
 			nSlots += 1;
 		if (operation != CMD_SELECT)
 			nSlots++;			/* for es_trig_tuple_slot */
-		if (parseTree->returningLists)
+		if (plannedstmt->returningLists)
 			nSlots++;			/* for RETURNING projection */
 
 		estate->es_tupleTable = ExecCreateTupleTable(nSlots);
@@ -617,7 +605,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	}
 
 	/* mark EvalPlanQual not active */
-	estate->es_topPlan = plan;
+	estate->es_plannedstmt = plannedstmt;
 	estate->es_evalPlanQual = NULL;
 	estate->es_evTupleNull = NULL;
 	estate->es_evTuple = NULL;
@@ -683,7 +671,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			 * junk filter.  Note this is only possible for UPDATE/DELETE, so
 			 * we can't be fooled by some needing a filter and some not.
 			 */
-			if (parseTree->resultRelations != NIL)
+			if (list_length(plannedstmt->resultRelations) > 1)
 			{
 				PlanState **appendplans;
 				int			as_nplans;
@@ -772,7 +760,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	/*
 	 * Initialize RETURNING projections if needed.
 	 */
-	if (parseTree->returningLists)
+	if (plannedstmt->returningLists)
 	{
 		TupleTableSlot *slot;
 		ExprContext *econtext;
@@ -782,7 +770,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		 * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case.
 		 * We assume all the sublists will generate the same output tupdesc.
 		 */
-		tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists),
+		tupType = ExecTypeFromTL((List *) linitial(plannedstmt->returningLists),
 								 false);
 
 		/* Set up a slot for the output of the RETURNING projection(s) */
@@ -795,9 +783,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		 * Build a projection for each result rel.	Note that any SubPlans in
 		 * the RETURNING lists get attached to the topmost plan node.
 		 */
-		Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations);
+		Assert(list_length(plannedstmt->returningLists) == estate->es_num_result_relations);
 		resultRelInfo = estate->es_result_relations;
-		foreach(l, parseTree->returningLists)
+		foreach(l, plannedstmt->returningLists)
 		{
 			List	   *rlist = (List *) lfirst(l);
 			List	   *rliststate;
@@ -2273,14 +2261,14 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
 	epqstate->es_into_relation_descriptor = estate->es_into_relation_descriptor;
 	epqstate->es_into_relation_use_wal = estate->es_into_relation_use_wal;
 	epqstate->es_param_list_info = estate->es_param_list_info;
-	if (estate->es_topPlan->nParamExec > 0)
+	if (estate->es_plannedstmt->nParamExec > 0)
 		epqstate->es_param_exec_vals = (ParamExecData *)
-			palloc0(estate->es_topPlan->nParamExec * sizeof(ParamExecData));
+			palloc0(estate->es_plannedstmt->nParamExec * sizeof(ParamExecData));
 	epqstate->es_rowMarks = estate->es_rowMarks;
 	epqstate->es_instrument = estate->es_instrument;
 	epqstate->es_select_into = estate->es_select_into;
 	epqstate->es_into_oids = estate->es_into_oids;
-	epqstate->es_topPlan = estate->es_topPlan;
+	epqstate->es_plannedstmt = estate->es_plannedstmt;
 
 	/*
 	 * Each epqstate must have its own es_evTupleNull state, but all the stack
@@ -2299,7 +2287,7 @@ EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq)
 	epqstate->es_tupleTable =
 		ExecCreateTupleTable(estate->es_tupleTable->size);
 
-	epq->planstate = ExecInitNode(estate->es_topPlan, epqstate, 0);
+	epq->planstate = ExecInitNode(estate->es_plannedstmt->planTree, epqstate, 0);
 
 	MemoryContextSwitchTo(oldcontext);
 }
@@ -2365,7 +2353,7 @@ typedef struct
 static void
 OpenIntoRel(QueryDesc *queryDesc)
 {
-	Query	   *parseTree = queryDesc->parsetree;
+	IntoClause *into = queryDesc->plannedstmt->into;
 	EState	   *estate = queryDesc->estate;
 	Relation	intoRelationDesc;
 	char	   *intoName;
@@ -2377,10 +2365,12 @@ OpenIntoRel(QueryDesc *queryDesc)
 	TupleDesc	tupdesc;
 	DR_intorel *myState;
 
+	Assert(into);
+
 	/*
 	 * Check consistency of arguments
 	 */
-	if (parseTree->intoOnCommit != ONCOMMIT_NOOP && !parseTree->into->istemp)
+	if (into->onCommit != ONCOMMIT_NOOP && !into->rel->istemp)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -2388,8 +2378,8 @@ OpenIntoRel(QueryDesc *queryDesc)
 	/*
 	 * Find namespace to create in, check its permissions
 	 */
-	intoName = parseTree->into->relname;
-	namespaceId = RangeVarGetCreationNamespace(parseTree->into);
+	intoName = into->rel->relname;
+	namespaceId = RangeVarGetCreationNamespace(into->rel);
 
 	aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
 									  ACL_CREATE);
@@ -2401,16 +2391,16 @@ OpenIntoRel(QueryDesc *queryDesc)
 	 * Select tablespace to use.  If not specified, use default_tablespace
 	 * (which may in turn default to database's default).
 	 */
-	if (parseTree->intoTableSpaceName)
+	if (into->tableSpaceName)
 	{
-		tablespaceId = get_tablespace_oid(parseTree->intoTableSpaceName);
+		tablespaceId = get_tablespace_oid(into->tableSpaceName);
 		if (!OidIsValid(tablespaceId))
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
 					 errmsg("tablespace \"%s\" does not exist",
-							parseTree->intoTableSpaceName)));
+							into->tableSpaceName)));
 	}
-	else if (parseTree->into->istemp)
+	else if (into->rel->istemp)
 	{
 		tablespaceId = GetTempTablespace();
 	}
@@ -2435,7 +2425,7 @@ OpenIntoRel(QueryDesc *queryDesc)
 
 	/* Parse and validate any reloptions */
 	reloptions = transformRelOptions((Datum) 0,
-									 parseTree->intoOptions,
+									 into->options,
 									 true,
 									 false);
 	(void) heap_reloptions(RELKIND_RELATION, reloptions, true);
@@ -2454,7 +2444,7 @@ OpenIntoRel(QueryDesc *queryDesc)
 											  false,
 											  true,
 											  0,
-											  parseTree->intoOnCommit,
+											  into->onCommit,
 											  reloptions,
 											  allowSystemTableMods);
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 91efba011ca..d9957573883 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.144 2007/02/06 17:35:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.145 2007/02/20 17:32:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -242,7 +242,7 @@ InternalCreateExecutorState(MemoryContext qcontext, bool is_subquery)
 
 	estate->es_per_tuple_exprcontext = NULL;
 
-	estate->es_topPlan = NULL;
+	estate->es_plannedstmt = NULL;
 	estate->es_evalPlanQual = NULL;
 	estate->es_evTupleNull = NULL;
 	estate->es_evTuple = NULL;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 78044b2d614..596a482fa13 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.110 2007/02/02 00:02:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.111 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,8 +33,8 @@
 
 /*
  * We have an execution_state record for each query in a function.	Each
- * record contains a querytree and plantree for its query.	If the query
- * is currently in F_EXEC_RUN state then there's a QueryDesc too.
+ * record contains a plantree for its query.  If the query is currently in
+ * F_EXEC_RUN state then there's a QueryDesc too.
  */
 typedef enum
 {
@@ -45,8 +45,7 @@ typedef struct local_es
 {
 	struct local_es *next;
 	ExecStatus	status;
-	Query	   *query;
-	Plan	   *plan;
+	Node	   *stmt;			/* PlannedStmt or utility statement */
 	QueryDesc  *qd;				/* null unless status == RUN */
 } execution_state;
 
@@ -105,26 +104,30 @@ init_execution_state(List *queryTree_list, bool readonly_func)
 	foreach(qtl_item, queryTree_list)
 	{
 		Query	   *queryTree = lfirst(qtl_item);
-		Plan	   *planTree;
+		Node	   *stmt;
 		execution_state *newes;
 
+		Assert(IsA(queryTree, Query));
+
+		if (queryTree->commandType == CMD_UTILITY)
+			stmt = queryTree->utilityStmt;
+		else
+			stmt = (Node *) pg_plan_query(queryTree, NULL);
+
 		/* Precheck all commands for validity in a function */
-		if (queryTree->commandType == CMD_UTILITY &&
-			IsA(queryTree->utilityStmt, TransactionStmt))
+		if (IsA(stmt, TransactionStmt))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			/* translator: %s is a SQL statement name */
 					 errmsg("%s is not allowed in a SQL function",
-							CreateQueryTag(queryTree))));
+							CreateCommandTag(stmt))));
 
-		if (readonly_func && !QueryIsReadOnly(queryTree))
+		if (readonly_func && !CommandIsReadOnly(stmt))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			/* translator: %s is a SQL statement name */
 					 errmsg("%s is not allowed in a non-volatile function",
-							CreateQueryTag(queryTree))));
-
-		planTree = pg_plan_query(queryTree, NULL);
+							CreateCommandTag(stmt))));
 
 		newes = (execution_state *) palloc(sizeof(execution_state));
 		if (preves)
@@ -134,8 +137,7 @@ init_execution_state(List *queryTree_list, bool readonly_func)
 
 		newes->next = NULL;
 		newes->status = F_EXEC_START;
-		newes->query = queryTree;
-		newes->plan = planTree;
+		newes->stmt = stmt;
 		newes->qd = NULL;
 
 		preves = newes;
@@ -298,10 +300,16 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 		snapshot = CopySnapshot(GetTransactionSnapshot());
 	}
 
-	es->qd = CreateQueryDesc(es->query, es->plan,
-							 snapshot, InvalidSnapshot,
-							 None_Receiver,
-							 fcache->paramLI, false);
+	if (IsA(es->stmt, PlannedStmt))
+		es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
+								 snapshot, InvalidSnapshot,
+								 None_Receiver,
+								 fcache->paramLI, false);
+	else
+		es->qd = CreateUtilityQueryDesc(es->stmt,
+										snapshot,
+										None_Receiver,
+										fcache->paramLI);
 
 	/* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
 
@@ -337,7 +345,7 @@ postquel_getnext(execution_state *es)
 
 		if (es->qd->operation == CMD_UTILITY)
 		{
-			ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
+			ProcessUtility(es->qd->utilitystmt, es->qd->params,
 						   es->qd->dest, NULL);
 			result = NULL;
 		}
@@ -351,7 +359,7 @@ postquel_getnext(execution_state *es)
 			 */
 			if (LAST_POSTQUEL_COMMAND(es) &&
 				es->qd->operation == CMD_SELECT &&
-				es->qd->parsetree->into == NULL)
+				es->qd->plannedstmt->into == NULL)
 				count = 1L;
 			else
 				count = 0L;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index a565ba3cd2b..33ec21286d9 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.169 2007/01/09 22:00:59 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.170 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -831,8 +831,7 @@ SPI_cursor_open(const char *name, void *plan,
 				bool read_only)
 {
 	_SPI_plan  *spiplan = (_SPI_plan *) plan;
-	List	   *qtlist;
-	List	   *ptlist;
+	List	   *stmt_list;
 	ParamListInfo paramLI;
 	Snapshot	snapshot;
 	MemoryContext oldcontext;
@@ -846,29 +845,22 @@ SPI_cursor_open(const char *name, void *plan,
 	if (!SPI_is_cursor_plan(spiplan))
 	{
 		/* try to give a good error message */
-		Query	   *queryTree;
+		Node	   *stmt;
 
-		if (list_length(spiplan->qtlist) != 1)
+		if (list_length(spiplan->stmt_list_list) != 1)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 					 errmsg("cannot open multi-query plan as cursor")));
-		queryTree = PortalListGetPrimaryQuery((List *) linitial(spiplan->qtlist));
-		if (queryTree == NULL)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-					 errmsg("cannot open empty query as cursor")));
+		stmt = PortalListGetPrimaryStmt((List *) linitial(spiplan->stmt_list_list));
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 		/* translator: %s is name of a SQL command, eg INSERT */
 				 errmsg("cannot open %s query as cursor",
-						CreateQueryTag(queryTree))));
+						CreateCommandTag(stmt))));
 	}
 
-	Assert(list_length(spiplan->qtlist) == 1);
-	qtlist = (List *) linitial(spiplan->qtlist);
-	ptlist = spiplan->ptlist;
-	if (list_length(qtlist) != list_length(ptlist))
-		elog(ERROR, "corrupted SPI plan lists");
+	Assert(list_length(spiplan->stmt_list_list) == 1);
+	stmt_list = (List *) linitial(spiplan->stmt_list_list);
 
 	/* Reset SPI result (note we deliberately don't touch lastoid) */
 	SPI_processed = 0;
@@ -888,10 +880,9 @@ SPI_cursor_open(const char *name, void *plan,
 		portal = CreatePortal(name, false, false);
 	}
 
-	/* Switch to portal's memory and copy the parsetrees and plans to there */
+	/* Switch to portal's memory and copy the plans to there */
 	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-	qtlist = copyObject(qtlist);
-	ptlist = copyObject(ptlist);
+	stmt_list = copyObject(stmt_list);
 
 	/* If the plan has parameters, set them up */
 	if (spiplan->nargs > 0)
@@ -934,9 +925,8 @@ SPI_cursor_open(const char *name, void *plan,
 	PortalDefineQuery(portal,
 					  NULL,		/* no statement name */
 					  spiplan->query,
-					  CreateQueryTag(PortalListGetPrimaryQuery(qtlist)),
-					  qtlist,
-					  ptlist,
+					  CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)),
+					  stmt_list,
 					  PortalGetHeapMemory(portal));
 
 	MemoryContextSwitchTo(oldcontext);
@@ -945,8 +935,9 @@ SPI_cursor_open(const char *name, void *plan,
 	 * Set up options for portal.
 	 */
 	portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
-	if (list_length(ptlist) == 1 &&
-		ExecSupportsBackwardScan((Plan *) linitial(ptlist)))
+	if (list_length(stmt_list) == 1 &&
+		IsA((Node *) linitial(stmt_list), PlannedStmt) &&
+		ExecSupportsBackwardScan(((PlannedStmt *) linitial(stmt_list))->planTree))
 		portal->cursorOptions |= CURSOR_OPT_SCROLL;
 	else
 		portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
@@ -1076,10 +1067,10 @@ SPI_is_cursor_plan(void *plan)
 		return false;
 	}
 
-	if (list_length(spiplan->qtlist) != 1)
+	if (list_length(spiplan->stmt_list_list) != 1)
 		return false;			/* not exactly 1 pre-rewrite command */
 
-	switch (ChoosePortalStrategy((List *) linitial(spiplan->qtlist)))
+	switch (ChoosePortalStrategy((List *) linitial(spiplan->stmt_list_list)))
 	{
 		case PORTAL_ONE_SELECT:
 		case PORTAL_ONE_RETURNING:
@@ -1257,14 +1248,13 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
  *
  * At entry, plan->argtypes and plan->nargs must be valid.
  *
- * Query and plan lists are stored into *plan.
+ * Result lists are stored into *plan.
  */
 static void
 _SPI_prepare_plan(const char *src, _SPI_plan *plan)
 {
 	List	   *raw_parsetree_list;
-	List	   *query_list_list;
-	List	   *plan_list;
+	List	   *stmt_list_list;
 	ListCell   *list_item;
 	ErrorContextCallback spierrcontext;
 	Oid		   *argtypes = plan->argtypes;
@@ -1294,12 +1284,11 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
 	/*
 	 * Do parse analysis and rule rewrite for each raw parsetree.
 	 *
-	 * We save the querytrees from each raw parsetree as a separate sublist.
+	 * We save the results from each raw parsetree as a separate sublist.
 	 * This allows _SPI_execute_plan() to know where the boundaries between
 	 * original queries fall.
 	 */
-	query_list_list = NIL;
-	plan_list = NIL;
+	stmt_list_list = NIL;
 
 	foreach(list_item, raw_parsetree_list)
 	{
@@ -1308,14 +1297,11 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
 
 		query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
 
-		query_list_list = lappend(query_list_list, query_list);
-
-		plan_list = list_concat(plan_list,
-								pg_plan_queries(query_list, NULL, false));
+		stmt_list_list = lappend(stmt_list_list,
+								 pg_plan_queries(query_list, NULL, false));
 	}
 
-	plan->qtlist = query_list_list;
-	plan->ptlist = plan_list;
+	plan->stmt_list_list = stmt_list_list;
 
 	/*
 	 * Pop the error context stack
@@ -1348,9 +1334,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 	saveActiveSnapshot = ActiveSnapshot;
 	PG_TRY();
 	{
-		List	   *query_list_list = plan->qtlist;
-		ListCell   *plan_list_item = list_head(plan->ptlist);
-		ListCell   *query_list_list_item;
+		ListCell   *stmt_list_list_item;
 		ErrorContextCallback spierrcontext;
 		int			nargs = plan->nargs;
 		ParamListInfo paramLI;
@@ -1386,57 +1370,61 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 		spierrcontext.previous = error_context_stack;
 		error_context_stack = &spierrcontext;
 
-		foreach(query_list_list_item, query_list_list)
+		foreach(stmt_list_list_item, plan->stmt_list_list)
 		{
-			List	   *query_list = lfirst(query_list_list_item);
-			ListCell   *query_list_item;
+			List	   *stmt_list = (List *) lfirst(stmt_list_list_item);
+			ListCell   *stmt_list_item;
 
-			foreach(query_list_item, query_list)
+			foreach(stmt_list_item, stmt_list)
 			{
-				Query	   *queryTree = (Query *) lfirst(query_list_item);
-				Plan	   *planTree;
+				Node	   *stmt = (Node *) lfirst(stmt_list_item);
+				bool		canSetTag;
 				QueryDesc  *qdesc;
 				DestReceiver *dest;
 
-				planTree = lfirst(plan_list_item);
-				plan_list_item = lnext(plan_list_item);
-
 				_SPI_current->processed = 0;
 				_SPI_current->lastoid = InvalidOid;
 				_SPI_current->tuptable = NULL;
 
-				if (queryTree->commandType == CMD_UTILITY)
+				if (IsA(stmt, PlannedStmt))
 				{
-					if (IsA(queryTree->utilityStmt, CopyStmt))
+					canSetTag = ((PlannedStmt *) stmt)->canSetTag;
+				}
+				else
+				{
+					/* utilities are canSetTag if only thing in list */
+					canSetTag = (list_length(stmt_list) == 1);
+
+					if (IsA(stmt, CopyStmt))
 					{
-						CopyStmt   *stmt = (CopyStmt *) queryTree->utilityStmt;
+						CopyStmt   *cstmt = (CopyStmt *) stmt;
 
-						if (stmt->filename == NULL)
+						if (cstmt->filename == NULL)
 						{
 							my_res = SPI_ERROR_COPY;
 							goto fail;
 						}
 					}
-					else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
-							 IsA(queryTree->utilityStmt, ClosePortalStmt) ||
-							 IsA(queryTree->utilityStmt, FetchStmt))
+					else if (IsA(stmt, DeclareCursorStmt) ||
+							 IsA(stmt, ClosePortalStmt) ||
+							 IsA(stmt, FetchStmt))
 					{
 						my_res = SPI_ERROR_CURSOR;
 						goto fail;
 					}
-					else if (IsA(queryTree->utilityStmt, TransactionStmt))
+					else if (IsA(stmt, TransactionStmt))
 					{
 						my_res = SPI_ERROR_TRANSACTION;
 						goto fail;
 					}
 				}
 
-				if (read_only && !QueryIsReadOnly(queryTree))
+				if (read_only && !CommandIsReadOnly(stmt))
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					/* translator: %s is a SQL statement name */
 					   errmsg("%s is not allowed in a non-volatile function",
-							  CreateQueryTag(queryTree))));
+							  CreateCommandTag(stmt))));
 
 				/*
 				 * If not read-only mode, advance the command counter before
@@ -1445,7 +1433,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 				if (!read_only)
 					CommandCounterIncrement();
 
-				dest = CreateDestReceiver(queryTree->canSetTag ? DestSPI : DestNone,
+				dest = CreateDestReceiver(canSetTag ? DestSPI : DestNone,
 										  NULL);
 
 				if (snapshot == InvalidSnapshot)
@@ -1471,26 +1459,24 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 						ActiveSnapshot->curcid = GetCurrentCommandId();
 				}
 
-				if (queryTree->commandType == CMD_UTILITY)
-				{
-					ProcessUtility(queryTree->utilityStmt, paramLI,
-								   dest, NULL);
-					/* Update "processed" if stmt returned tuples */
-					if (_SPI_current->tuptable)
-						_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free;
-					res = SPI_OK_UTILITY;
-				}
-				else
+				if (IsA(stmt, PlannedStmt))
 				{
-					qdesc = CreateQueryDesc(queryTree, planTree,
+					qdesc = CreateQueryDesc((PlannedStmt *) stmt,
 											ActiveSnapshot,
 											crosscheck_snapshot,
 											dest,
 											paramLI, false);
-					res = _SPI_pquery(qdesc,
-									  queryTree->canSetTag ? tcount : 0);
+					res = _SPI_pquery(qdesc, canSetTag ? tcount : 0);
 					FreeQueryDesc(qdesc);
 				}
+				else
+				{
+					ProcessUtility(stmt, paramLI, dest, NULL);
+					/* Update "processed" if stmt returned tuples */
+					if (_SPI_current->tuptable)
+						_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free;
+					res = SPI_OK_UTILITY;
+				}
 				FreeSnapshot(ActiveSnapshot);
 				ActiveSnapshot = NULL;
 
@@ -1499,7 +1485,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 				 * the caller.	Be careful to free any tuptables not returned,
 				 * to avoid intratransaction memory leak.
 				 */
-				if (queryTree->canSetTag)
+				if (canSetTag)
 				{
 					my_processed = _SPI_current->processed;
 					my_lastoid = _SPI_current->lastoid;
@@ -1565,7 +1551,7 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount)
 	switch (operation)
 	{
 		case CMD_SELECT:
-			if (queryDesc->parsetree->into)		/* select into table? */
+			if (queryDesc->plannedstmt->into)		/* select into table? */
 				res = SPI_OK_SELINTO;
 			else if (queryDesc->dest->mydest != DestSPI)
 			{
@@ -1576,19 +1562,19 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount)
 				res = SPI_OK_SELECT;
 			break;
 		case CMD_INSERT:
-			if (queryDesc->parsetree->returningList)
+			if (queryDesc->plannedstmt->returningLists)
 				res = SPI_OK_INSERT_RETURNING;
 			else
 				res = SPI_OK_INSERT;
 			break;
 		case CMD_DELETE:
-			if (queryDesc->parsetree->returningList)
+			if (queryDesc->plannedstmt->returningLists)
 				res = SPI_OK_DELETE_RETURNING;
 			else
 				res = SPI_OK_DELETE;
 			break;
 		case CMD_UPDATE:
-			if (queryDesc->parsetree->returningList)
+			if (queryDesc->plannedstmt->returningLists)
 				res = SPI_OK_UPDATE_RETURNING;
 			else
 				res = SPI_OK_UPDATE;
@@ -1611,7 +1597,7 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount)
 	_SPI_current->processed = queryDesc->estate->es_processed;
 	_SPI_current->lastoid = queryDesc->estate->es_lastoid;
 
-	if ((res == SPI_OK_SELECT || queryDesc->parsetree->returningList) &&
+	if ((res == SPI_OK_SELECT || queryDesc->plannedstmt->returningLists) &&
 		queryDesc->dest->mydest == DestSPI)
 	{
 		if (_SPI_checktuples())
@@ -1813,8 +1799,7 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
 	newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
 	newplan->plancxt = plancxt;
 	newplan->query = pstrdup(plan->query);
-	newplan->qtlist = (List *) copyObject(plan->qtlist);
-	newplan->ptlist = (List *) copyObject(plan->ptlist);
+	newplan->stmt_list_list = (List *) copyObject(plan->stmt_list_list);
 	newplan->nargs = plan->nargs;
 	if (plan->nargs > 0)
 	{
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index cdf98de568a..4d10afc022d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.366 2007/02/19 02:23:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.367 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,6 +64,27 @@
  * ****************************************************************
  */
 
+/*
+ * _copyPlannedStmt
+ */
+static PlannedStmt *
+_copyPlannedStmt(PlannedStmt *from)
+{
+	PlannedStmt	   *newnode = makeNode(PlannedStmt);
+
+	COPY_SCALAR_FIELD(commandType);
+	COPY_SCALAR_FIELD(canSetTag);
+	COPY_NODE_FIELD(planTree);
+	COPY_NODE_FIELD(rtable);
+	COPY_NODE_FIELD(resultRelations);
+	COPY_NODE_FIELD(into);
+	COPY_NODE_FIELD(returningLists);
+	COPY_NODE_FIELD(rowMarks);
+	COPY_SCALAR_FIELD(nParamExec);
+
+	return newnode;
+}
+
 /*
  * CopyPlanFields
  *
@@ -84,7 +105,6 @@ CopyPlanFields(Plan *from, Plan *newnode)
 	COPY_NODE_FIELD(initPlan);
 	COPY_BITMAPSET_FIELD(extParam);
 	COPY_BITMAPSET_FIELD(allParam);
-	COPY_SCALAR_FIELD(nParamExec);
 }
 
 /*
@@ -698,6 +718,23 @@ _copyRangeVar(RangeVar *from)
 	return newnode;
 }
 
+/*
+ * _copyIntoClause
+ */
+static IntoClause *
+_copyIntoClause(IntoClause *from)
+{
+	IntoClause   *newnode = makeNode(IntoClause);
+
+	COPY_NODE_FIELD(rel);
+	COPY_NODE_FIELD(colNames);
+	COPY_NODE_FIELD(options);
+	COPY_SCALAR_FIELD(onCommit);
+	COPY_STRING_FIELD(tableSpaceName);
+
+	return newnode;
+}
+
 /*
  * We don't need a _copyExpr because Expr is an abstract supertype which
  * should never actually get instantiated.	Also, since it has no common
@@ -1762,9 +1799,6 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
 	COPY_NODE_FIELD(into);
-	COPY_NODE_FIELD(intoOptions);
-	COPY_SCALAR_FIELD(intoOnCommit);
-	COPY_STRING_FIELD(intoTableSpaceName);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasSubLinks);
 	COPY_NODE_FIELD(rtable);
@@ -1779,8 +1813,6 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(limitCount);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(setOperations);
-	COPY_NODE_FIELD(resultRelations);
-	COPY_NODE_FIELD(returningLists);
 
 	return newnode;
 }
@@ -1832,10 +1864,6 @@ _copySelectStmt(SelectStmt *from)
 
 	COPY_NODE_FIELD(distinctClause);
 	COPY_NODE_FIELD(into);
-	COPY_NODE_FIELD(intoColNames);
-	COPY_NODE_FIELD(intoOptions);
-	COPY_SCALAR_FIELD(intoOnCommit);
-	COPY_STRING_FIELD(intoTableSpaceName);
 	COPY_NODE_FIELD(targetList);
 	COPY_NODE_FIELD(fromClause);
 	COPY_NODE_FIELD(whereClause);
@@ -2768,9 +2796,6 @@ _copyExecuteStmt(ExecuteStmt *from)
 
 	COPY_STRING_FIELD(name);
 	COPY_NODE_FIELD(into);
-	COPY_NODE_FIELD(intoOptions);
-	COPY_SCALAR_FIELD(into_on_commit);
-	COPY_STRING_FIELD(into_tbl_space);
 	COPY_NODE_FIELD(params);
 
 	return newnode;
@@ -2902,6 +2927,9 @@ copyObject(void *from)
 			/*
 			 * PLAN NODES
 			 */
+		case T_PlannedStmt:
+			retval = _copyPlannedStmt(from);
+			break;
 		case T_Plan:
 			retval = _copyPlan(from);
 			break;
@@ -2990,6 +3018,9 @@ copyObject(void *from)
 		case T_RangeVar:
 			retval = _copyRangeVar(from);
 			break;
+		case T_IntoClause:
+			retval = _copyIntoClause(from);
+			break;
 		case T_Var:
 			retval = _copyVar(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c17a40bbbb2..dafa15f0287 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.298 2007/02/03 14:06:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.299 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -102,6 +102,18 @@ _equalRangeVar(RangeVar *a, RangeVar *b)
 	return true;
 }
 
+static bool
+_equalIntoClause(IntoClause *a, IntoClause *b)
+{
+	COMPARE_NODE_FIELD(rel);
+	COMPARE_NODE_FIELD(colNames);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_SCALAR_FIELD(onCommit);
+	COMPARE_STRING_FIELD(tableSpaceName);
+
+	return true;
+}
+
 /*
  * We don't need an _equalExpr because Expr is an abstract supertype which
  * should never actually get instantiated.	Also, since it has no common
@@ -690,9 +702,6 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
 	COMPARE_NODE_FIELD(into);
-	COMPARE_NODE_FIELD(intoOptions);
-	COMPARE_SCALAR_FIELD(intoOnCommit);
-	COMPARE_STRING_FIELD(intoTableSpaceName);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
 	COMPARE_NODE_FIELD(rtable);
@@ -707,8 +716,6 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(limitCount);
 	COMPARE_NODE_FIELD(rowMarks);
 	COMPARE_NODE_FIELD(setOperations);
-	COMPARE_NODE_FIELD(resultRelations);
-	COMPARE_NODE_FIELD(returningLists);
 
 	return true;
 }
@@ -752,10 +759,6 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 {
 	COMPARE_NODE_FIELD(distinctClause);
 	COMPARE_NODE_FIELD(into);
-	COMPARE_NODE_FIELD(intoColNames);
-	COMPARE_NODE_FIELD(intoOptions);
-	COMPARE_SCALAR_FIELD(intoOnCommit);
-	COMPARE_STRING_FIELD(intoTableSpaceName);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_NODE_FIELD(fromClause);
 	COMPARE_NODE_FIELD(whereClause);
@@ -1545,9 +1548,6 @@ _equalExecuteStmt(ExecuteStmt *a, ExecuteStmt *b)
 {
 	COMPARE_STRING_FIELD(name);
 	COMPARE_NODE_FIELD(into);
-	COMPARE_NODE_FIELD(intoOptions);
-	COMPARE_SCALAR_FIELD(into_on_commit);
-	COMPARE_STRING_FIELD(into_tbl_space);
 	COMPARE_NODE_FIELD(params);
 
 	return true;
@@ -1967,6 +1967,9 @@ equal(void *a, void *b)
 		case T_RangeVar:
 			retval = _equalRangeVar(a, b);
 			break;
+		case T_IntoClause:
+			retval = _equalIntoClause(a, b);
+			break;
 		case T_Var:
 			retval = _equalVar(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0600f63b556..c8cc25abb13 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.299 2007/02/19 07:03:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.300 2007/02/20 17:32:15 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -234,6 +234,22 @@ _outDatum(StringInfo str, Datum value, int typlen, bool typbyval)
  *	Stuff from plannodes.h
  */
 
+static void
+_outPlannedStmt(StringInfo str, PlannedStmt *node)
+{
+	WRITE_NODE_TYPE("PLANNEDSTMT");
+
+	WRITE_ENUM_FIELD(commandType, CmdType);
+	WRITE_BOOL_FIELD(canSetTag);
+	WRITE_NODE_FIELD(planTree);
+	WRITE_NODE_FIELD(rtable);
+	WRITE_NODE_FIELD(resultRelations);
+	WRITE_NODE_FIELD(into);
+	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(rowMarks);
+	WRITE_INT_FIELD(nParamExec);
+}
+
 /*
  * print the basic stuff of all nodes that inherit from Plan
  */
@@ -251,7 +267,6 @@ _outPlanInfo(StringInfo str, Plan *node)
 	WRITE_NODE_FIELD(initPlan);
 	WRITE_BITMAPSET_FIELD(extParam);
 	WRITE_BITMAPSET_FIELD(allParam);
-	WRITE_INT_FIELD(nParamExec);
 }
 
 /*
@@ -635,6 +650,18 @@ _outRangeVar(StringInfo str, RangeVar *node)
 	WRITE_NODE_FIELD(alias);
 }
 
+static void
+_outIntoClause(StringInfo str, IntoClause *node)
+{
+	WRITE_NODE_TYPE("INTOCLAUSE");
+
+	WRITE_NODE_FIELD(rel);
+	WRITE_NODE_FIELD(colNames);
+	WRITE_NODE_FIELD(options);
+	WRITE_ENUM_FIELD(onCommit, OnCommitAction);
+	WRITE_STRING_FIELD(tableSpaceName);
+}
+
 static void
 _outVar(StringInfo str, Var *node)
 {
@@ -1245,6 +1272,8 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
 	WRITE_NODE_FIELD(glob);
 	WRITE_UINT_FIELD(query_level);
 	WRITE_NODE_FIELD(join_rel_list);
+	WRITE_NODE_FIELD(resultRelations);
+	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(init_plans);
 	WRITE_NODE_FIELD(eq_classes);
 	WRITE_NODE_FIELD(canon_pathkeys);
@@ -1502,10 +1531,6 @@ _outSelectStmt(StringInfo str, SelectStmt *node)
 
 	WRITE_NODE_FIELD(distinctClause);
 	WRITE_NODE_FIELD(into);
-	WRITE_NODE_FIELD(intoColNames);
-	WRITE_NODE_FIELD(intoOptions);
-	WRITE_ENUM_FIELD(intoOnCommit, OnCommitAction);
-	WRITE_STRING_FIELD(intoTableSpaceName);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_NODE_FIELD(fromClause);
 	WRITE_NODE_FIELD(whereClause);
@@ -1651,9 +1676,6 @@ _outQuery(StringInfo str, Query *node)
 
 	WRITE_INT_FIELD(resultRelation);
 	WRITE_NODE_FIELD(into);
-	WRITE_NODE_FIELD(intoOptions);
-	WRITE_ENUM_FIELD(intoOnCommit, OnCommitAction);
-	WRITE_STRING_FIELD(intoTableSpaceName);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasSubLinks);
 	WRITE_NODE_FIELD(rtable);
@@ -1668,8 +1690,6 @@ _outQuery(StringInfo str, Query *node)
 	WRITE_NODE_FIELD(limitCount);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(setOperations);
-	WRITE_NODE_FIELD(resultRelations);
-	WRITE_NODE_FIELD(returningLists);
 }
 
 static void
@@ -1988,6 +2008,9 @@ _outNode(StringInfo str, void *obj)
 		appendStringInfoChar(str, '{');
 		switch (nodeTag(obj))
 		{
+			case T_PlannedStmt:
+				_outPlannedStmt(str, obj);
+				break;
 			case T_Plan:
 				_outPlan(str, obj);
 				break;
@@ -2072,6 +2095,9 @@ _outNode(StringInfo str, void *obj)
 			case T_RangeVar:
 				_outRangeVar(str, obj);
 				break;
+			case T_IntoClause:
+				_outIntoClause(str, obj);
+				break;
 			case T_Var:
 				_outVar(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 17d36b4efe6..6478dce4b1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.202 2007/02/03 14:06:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.203 2007/02/20 17:32:15 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -140,9 +140,6 @@ _readQuery(void)
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
 	READ_NODE_FIELD(into);
-	READ_NODE_FIELD(intoOptions);
-	READ_ENUM_FIELD(intoOnCommit, OnCommitAction);
-	READ_STRING_FIELD(intoTableSpaceName);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasSubLinks);
 	READ_NODE_FIELD(rtable);
@@ -157,8 +154,6 @@ _readQuery(void)
 	READ_NODE_FIELD(limitCount);
 	READ_NODE_FIELD(rowMarks);
 	READ_NODE_FIELD(setOperations);
-	READ_NODE_FIELD(resultRelations);
-	READ_NODE_FIELD(returningLists);
 
 	READ_DONE();
 }
@@ -287,6 +282,20 @@ _readRangeVar(void)
 	READ_DONE();
 }
 
+static IntoClause *
+_readIntoClause(void)
+{
+	READ_LOCALS(IntoClause);
+
+	READ_NODE_FIELD(rel);
+	READ_NODE_FIELD(colNames);
+	READ_NODE_FIELD(options);
+	READ_ENUM_FIELD(onCommit, OnCommitAction);
+	READ_STRING_FIELD(tableSpaceName);
+
+	READ_DONE();
+}
+
 /*
  * _readVar
  */
@@ -984,6 +993,8 @@ parseNodeString(void)
 		return_value = _readAlias();
 	else if (MATCH("RANGEVAR", 8))
 		return_value = _readRangeVar();
+	else if (MATCH("INTOCLAUSE", 10))
+		return_value = _readIntoClause();
 	else if (MATCH("VAR", 3))
 		return_value = _readVar();
 	else if (MATCH("CONST", 5))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 1cb1d86be1b..6c6e80071c1 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.159 2007/02/19 07:03:28 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.160 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -444,8 +444,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	Query	   *subquery = rte->subquery;
 	bool	   *differentTypes;
 	double		tuple_fraction;
+	PlannerInfo *subroot;
 	List	   *pathkeys;
-	List	   *subquery_pathkeys;
 
 	/* We need a workspace for keeping track of set-op type coercions */
 	differentTypes = (bool *)
@@ -520,7 +520,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	rel->subplan = subquery_planner(root->glob, subquery,
 									root->query_level + 1,
 									tuple_fraction,
-									&subquery_pathkeys);
+									&subroot);
 
 	/* Copy number of output rows from subplan */
 	rel->tuples = rel->subplan->plan_rows;
@@ -529,7 +529,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	set_baserel_size_estimates(root, rel);
 
 	/* Convert subquery pathkeys to outer representation */
-	pathkeys = convert_subquery_pathkeys(root, rel, subquery_pathkeys);
+	pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(rel, pathkeys));
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index be26d17a28a..406cc9dd496 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.27 2007/02/19 07:03:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.28 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -453,8 +453,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
 	subroot.init_plans = NIL;
 	subparse->commandType = CMD_SELECT;
 	subparse->resultRelation = 0;
-	subparse->resultRelations = NIL;
-	subparse->returningLists = NIL;
+	subparse->returningList = NIL;
 	subparse->into = NULL;
 	subparse->hasAggs = false;
 	subparse->groupClause = NIL;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f4a940175ea..3bb603a0f61 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.213 2007/02/19 07:03:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.214 2007/02/20 17:32:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,13 +79,15 @@ static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
  *	   Query optimizer entry point
  *
  *****************************************************************************/
-Plan *
+PlannedStmt *
 planner(Query *parse, bool isCursor, int cursorOptions,
 		ParamListInfo boundParams)
 {
+	PlannedStmt *result;
 	PlannerGlobal *glob;
 	double		tuple_fraction;
-	Plan	   *result_plan;
+	PlannerInfo *root;
+	Plan	   *top_plan;
 
 	/*
 	 * Set up global state for this planner invocation.  This data is needed
@@ -117,7 +119,7 @@ planner(Query *parse, bool isCursor, int cursorOptions,
 	}
 
 	/* primary planning entry point (may recurse for subqueries) */
-	result_plan = subquery_planner(glob, parse, 1, tuple_fraction, NULL);
+	top_plan = subquery_planner(glob, parse, 1, tuple_fraction, &root);
 
 	/*
 	 * If creating a plan for a scrollable cursor, make sure it can run
@@ -125,17 +127,27 @@ planner(Query *parse, bool isCursor, int cursorOptions,
 	 */
 	if (isCursor && (cursorOptions & CURSOR_OPT_SCROLL))
 	{
-		if (!ExecSupportsBackwardScan(result_plan))
-			result_plan = materialize_finished_plan(result_plan);
+		if (!ExecSupportsBackwardScan(top_plan))
+			top_plan = materialize_finished_plan(top_plan);
 	}
 
 	/* final cleanup of the plan */
-	result_plan = set_plan_references(result_plan, parse->rtable);
-
-	/* executor wants to know total number of Params used overall */
-	result_plan->nParamExec = list_length(glob->paramlist);
-
-	return result_plan;
+	top_plan = set_plan_references(top_plan, parse->rtable);
+
+	/* build the PlannedStmt result */
+	result = makeNode(PlannedStmt);
+
+	result->commandType = parse->commandType;
+	result->canSetTag = parse->canSetTag;
+	result->planTree = top_plan;
+	result->rtable = parse->rtable;
+	result->resultRelations = root->resultRelations;
+	result->into = parse->into;
+	result->returningLists = root->returningLists;
+	result->rowMarks = parse->rowMarks;
+	result->nParamExec = list_length(glob->paramlist);
+
+	return result;
 }
 
 
@@ -150,8 +162,8 @@ planner(Query *parse, bool isCursor, int cursorOptions,
  * tuple_fraction is the fraction of tuples we expect will be retrieved.
  * tuple_fraction is interpreted as explained for grouping_planner, below.
  *
- * If subquery_pathkeys isn't NULL, it receives a list of pathkeys indicating
- * the output sort ordering of the completed plan.
+ * If subroot isn't NULL, we pass back the query's final PlannerInfo struct;
+ * among other things this tells the output sort ordering of the plan.
  *
  * Basically, this routine does the stuff that should only be done once
  * per Query object.  It then calls grouping_planner.  At one time,
@@ -168,7 +180,7 @@ planner(Query *parse, bool isCursor, int cursorOptions,
 Plan *
 subquery_planner(PlannerGlobal *glob, Query *parse,
 				 Index level, double tuple_fraction,
-				 List **subquery_pathkeys)
+				 PlannerInfo **subroot)
 {
 	int			saved_plan_id = glob->next_plan_id;
 	PlannerInfo *root;
@@ -375,9 +387,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	if (root->glob->next_plan_id != saved_plan_id || root->query_level > 1)
 		SS_finalize_plan(root, plan);
 
-	/* Return sort ordering info if caller wants it */
-	if (subquery_pathkeys)
-		*subquery_pathkeys = root->query_pathkeys;
+	/* Return internal info if caller wants it */
+	if (subroot)
+		*subroot = root;
 
 	return plan;
 }
@@ -593,14 +605,14 @@ inheritance_planner(PlannerInfo *root)
 		/* Build list of per-relation RETURNING targetlists */
 		if (parse->returningList)
 		{
-			Assert(list_length(subroot.parse->returningLists) == 1);
+			Assert(list_length(subroot.returningLists) == 1);
 			returningLists = list_concat(returningLists,
-										 subroot.parse->returningLists);
+										 subroot.returningLists);
 		}
 	}
 
-	parse->resultRelations = resultRelations;
-	parse->returningLists = returningLists;
+	root->resultRelations = resultRelations;
+	root->returningLists = returningLists;
 
 	/* Mark result as unordered (probably unnecessary) */
 	root->query_pathkeys = NIL;
@@ -1101,8 +1113,16 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		rlist = set_returning_clause_references(parse->returningList,
 												result_plan,
 												parse->resultRelation);
-		parse->returningLists = list_make1(rlist);
+		root->returningLists = list_make1(rlist);
 	}
+	else
+		root->returningLists = NIL;
+
+	/* Compute result-relations list if needed */
+	if (parse->resultRelation)
+		root->resultRelations = list_make1_int(parse->resultRelation);
+	else
+		root->resultRelations = NIL;
 
 	/*
 	 * Return the actual output ordering in query_pathkeys for possible use by
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0eb81d6d898..63a5999a401 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.360 2007/02/01 19:10:27 momjian Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.361 2007/02/20 17:32:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2143,11 +2143,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (stmt->into)
 	{
 		qry->into = stmt->into;
-		if (stmt->intoColNames)
-			applyColumnNames(qry->targetList, stmt->intoColNames);
-		qry->intoOptions = copyObject(stmt->intoOptions);
-		qry->intoOnCommit = stmt->intoOnCommit;
-		qry->intoTableSpaceName = stmt->intoTableSpaceName;
+		if (stmt->into->colNames)
+			applyColumnNames(qry->targetList, stmt->into->colNames);
 	}
 
 	qry->rtable = pstate->p_rtable;
@@ -2315,11 +2312,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	if (stmt->into)
 	{
 		qry->into = stmt->into;
-		if (stmt->intoColNames)
-			applyColumnNames(qry->targetList, stmt->intoColNames);
-		qry->intoOptions = copyObject(stmt->intoOptions);
-		qry->intoOnCommit = stmt->intoOnCommit;
-		qry->intoTableSpaceName = stmt->intoTableSpaceName;
+		if (stmt->into->colNames)
+			applyColumnNames(qry->targetList, stmt->into->colNames);
 	}
 
 	/*
@@ -2399,9 +2393,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 
 	/*
 	 * Find leftmost leaf SelectStmt; extract the one-time-only items from it
-	 * and from the top-level node.  (Most of the INTO options can be
-	 * transferred to the Query immediately, but intoColNames has to be saved
-	 * to apply below.)
+	 * and from the top-level node.
 	 */
 	leftmostSelect = stmt->larg;
 	while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
@@ -2411,10 +2403,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	if (leftmostSelect->into)
 	{
 		qry->into = leftmostSelect->into;
-		intoColNames = leftmostSelect->intoColNames;
-		qry->intoOptions = copyObject(leftmostSelect->intoOptions);
-		qry->intoOnCommit = leftmostSelect->intoOnCommit;
-		qry->intoTableSpaceName = leftmostSelect->intoTableSpaceName;
+		intoColNames = leftmostSelect->into->colNames;
 	}
 
 	/* clear this to prevent complaints in transformSetOperationTree() */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cf25c5607a6..3204d0a401a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.579 2007/02/03 14:06:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.580 2007/02/20 17:32:16 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -139,6 +139,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 	IndexElem			*ielem;
 	Alias				*alias;
 	RangeVar			*range;
+	IntoClause			*into;
 	A_Indices			*aind;
 	ResTarget			*target;
 	PrivTarget			*privtarget;
@@ -248,7 +249,8 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 				prep_type_clause
 				execute_param_clause using_clause returning_clause
 
-%type <range>	into_clause OptTempTableName
+%type <range>	OptTempTableName
+%type <into>	into_clause create_as_target
 
 %type <defelt>	createfunc_opt_item common_func_opt_item
 %type <fun_param> func_arg
@@ -2253,8 +2255,7 @@ OptConsTableSpace:   USING INDEX TABLESPACE name	{ $$ = $4; }
  */
 
 CreateAsStmt:
-		CREATE OptTemp TABLE qualified_name OptCreateAs
-			OptWith OnCommitOption OptTableSpace AS SelectStmt
+		CREATE OptTemp TABLE create_as_target AS SelectStmt
 				{
 					/*
 					 * When the SelectStmt is a set-operation tree, we must
@@ -2263,18 +2264,26 @@ CreateAsStmt:
 					 * to find it.	Similarly, the output column names must
 					 * be attached to that Select's target list.
 					 */
-					SelectStmt *n = findLeftmostSelect((SelectStmt *) $10);
+					SelectStmt *n = findLeftmostSelect((SelectStmt *) $6);
 					if (n->into != NULL)
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
 								 errmsg("CREATE TABLE AS cannot specify INTO")));
-					$4->istemp = $2;
+					$4->rel->istemp = $2;
 					n->into = $4;
-					n->intoColNames = $5;
-					n->intoOptions = $6;
-					n->intoOnCommit = $7;
-					n->intoTableSpaceName = $8;
-					$$ = $10;
+					$$ = $6;
+				}
+		;
+
+create_as_target:
+			qualified_name OptCreateAs OptWith OnCommitOption OptTableSpace
+				{
+					$$ = makeNode(IntoClause);
+					$$->rel = $1;
+					$$->colNames = $2;
+					$$->options = $3;
+					$$->onCommit = $4;
+					$$->tableSpaceName = $5;
 				}
 		;
 
@@ -5459,19 +5468,15 @@ ExecuteStmt: EXECUTE name execute_param_clause
 					n->into = NULL;
 					$$ = (Node *) n;
 				}
-			| CREATE OptTemp TABLE qualified_name OptCreateAs
-				OptWith OnCommitOption OptTableSpace AS
+			| CREATE OptTemp TABLE create_as_target AS
 				EXECUTE name execute_param_clause
 				{
 					ExecuteStmt *n = makeNode(ExecuteStmt);
-					n->name = $11;
-					n->params = $12;
-					$4->istemp = $2;
+					n->name = $7;
+					n->params = $8;
+					$4->rel->istemp = $2;
 					n->into = $4;
-					n->intoOptions = $6;
-					n->into_on_commit = $7;
-					n->into_tbl_space = $8;
-					if ($5)
+					if ($4->colNames)
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 								 errmsg("column name list not allowed in CREATE TABLE / AS EXECUTE")));
@@ -5854,7 +5859,6 @@ simple_select:
 					n->distinctClause = $2;
 					n->targetList = $3;
 					n->into = $4;
-					n->intoColNames = NIL;
 					n->fromClause = $5;
 					n->whereClause = $6;
 					n->groupClause = $7;
@@ -5877,8 +5881,17 @@ simple_select:
 		;
 
 into_clause:
-			INTO OptTempTableName					{ $$ = $2; }
-			| /*EMPTY*/								{ $$ = NULL; }
+			INTO OptTempTableName
+				{
+					$$ = makeNode(IntoClause);
+					$$->rel = $2;
+					$$->colNames = NIL;
+					$$->options = NIL;
+					$$->onCommit = ONCOMMIT_NOOP;
+					$$->tableSpaceName = NULL;
+				}
+			| /*EMPTY*/
+				{ $$ = NULL; }
 		;
 
 /*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index f00897ee622..1c40a8752e9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.524 2007/02/17 19:33:32 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.525 2007/02/20 17:32:16 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -165,8 +165,7 @@ static int	InteractiveBackend(StringInfo inBuf);
 static int	SocketBackend(StringInfo inBuf);
 static int	ReadCommand(StringInfo inBuf);
 static List *pg_rewrite_queries(List *querytree_list);
-static bool check_log_statement_raw(List *raw_parsetree_list);
-static bool check_log_statement_cooked(List *parsetree_list);
+static bool check_log_statement(List *stmt_list);
 static int	errdetail_execute(List *raw_parsetree_list);
 static int	errdetail_params(ParamListInfo params);
 static void start_xact_command(void);
@@ -659,10 +658,10 @@ pg_rewrite_queries(List *querytree_list)
 
 
 /* Generate a plan for a single already-rewritten query. */
-Plan *
+PlannedStmt *
 pg_plan_query(Query *querytree, ParamListInfo boundParams)
 {
-	Plan	   *plan;
+	PlannedStmt *plan;
 
 	/* Utility commands have no plans. */
 	if (querytree->commandType == CMD_UTILITY)
@@ -680,7 +679,7 @@ pg_plan_query(Query *querytree, ParamListInfo boundParams)
 #ifdef COPY_PARSE_PLAN_TREES
 	/* Optional debugging check: pass plan output through copyObject() */
 	{
-		Plan	   *new_plan = (Plan *) copyObject(plan);
+		PlannedStmt *new_plan = (PlannedStmt *) copyObject(plan);
 
 		/*
 		 * equal() currently does not have routines to compare Plan nodes, so
@@ -715,23 +714,26 @@ pg_plan_query(Query *querytree, ParamListInfo boundParams)
  * utility statements depend on not having frozen the snapshot yet.
  * (We assume that such statements cannot appear together with plannable
  * statements in the rewriter's output.)
+ *
+ * Normal optimizable statements generate PlannedStmt entries in the result
+ * list.  Utility statements are simply represented by their statement nodes.
  */
 List *
 pg_plan_queries(List *querytrees, ParamListInfo boundParams,
 				bool needSnapshot)
 {
-	List	   *plan_list = NIL;
+	List	   *stmt_list = NIL;
 	ListCell   *query_list;
 
 	foreach(query_list, querytrees)
 	{
 		Query	   *query = (Query *) lfirst(query_list);
-		Plan	   *plan;
+		Node	   *stmt;
 
 		if (query->commandType == CMD_UTILITY)
 		{
 			/* Utility commands have no plans. */
-			plan = NULL;
+			stmt = query->utilityStmt;
 		}
 		else
 		{
@@ -740,13 +742,13 @@ pg_plan_queries(List *querytrees, ParamListInfo boundParams,
 				ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
 				needSnapshot = false;
 			}
-			plan = pg_plan_query(query, boundParams);
+			stmt = (Node *) pg_plan_query(query, boundParams);
 		}
 
-		plan_list = lappend(plan_list, plan);
+		stmt_list = lappend(stmt_list, stmt);
 	}
 
-	return plan_list;
+	return stmt_list;
 }
 
 
@@ -817,7 +819,7 @@ exec_simple_query(const char *query_string)
 	parsetree_list = pg_parse_query(query_string);
 
 	/* Log immediately if dictated by log_statement */
-	if (check_log_statement_raw(parsetree_list))
+	if (check_log_statement(parsetree_list))
 	{
 		ereport(LOG,
 				(errmsg("statement: %s", query_string),
@@ -905,7 +907,6 @@ exec_simple_query(const char *query_string)
 						  NULL,
 						  query_string,
 						  commandTag,
-						  querytree_list,
 						  plantree_list,
 						  MessageContext);
 
@@ -1050,9 +1051,10 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	List	   *parsetree_list;
 	const char *commandTag;
 	List	   *querytree_list,
-			   *plantree_list,
+			   *stmt_list,
 			   *param_list;
 	bool		is_named;
+	bool		fully_planned;
 	bool		save_log_statement_stats = log_statement_stats;
 	char		msec_str[32];
 
@@ -1202,17 +1204,23 @@ exec_parse_message(const char *query_string,	/* string to execute */
 		 * planning until Bind.  Otherwise do it now.
 		 */
 		if (!is_named && numParams > 0)
-			plantree_list = NIL;
+		{
+			stmt_list = querytree_list;
+			fully_planned = false;
+		}
 		else
-			plantree_list = pg_plan_queries(querytree_list, NULL, true);
+		{
+			stmt_list = pg_plan_queries(querytree_list, NULL, true);
+			fully_planned = true;
+		}
 	}
 	else
 	{
 		/* Empty input string.	This is legal. */
 		commandTag = NULL;
-		querytree_list = NIL;
-		plantree_list = NIL;
+		stmt_list = NIL;
 		param_list = NIL;
+		fully_planned = true;
 	}
 
 	/* If we got a cancel signal in analysis or planning, quit */
@@ -1226,9 +1234,9 @@ exec_parse_message(const char *query_string,	/* string to execute */
 		StorePreparedStatement(stmt_name,
 							   query_string,
 							   commandTag,
-							   querytree_list,
-							   plantree_list,
+							   stmt_list,
 							   param_list,
+							   fully_planned,
 							   false);
 	}
 	else
@@ -1240,9 +1248,9 @@ exec_parse_message(const char *query_string,	/* string to execute */
 		pstmt->query_string = pstrdup(query_string);
 		/* the rest is there already */
 		pstmt->commandTag = commandTag;
-		pstmt->query_list = querytree_list;
-		pstmt->plan_list = plantree_list;
+		pstmt->stmt_list = stmt_list;
 		pstmt->argtype_list = param_list;
+		pstmt->fully_planned = fully_planned;
 		pstmt->from_sql = false;
 		pstmt->context = unnamed_stmt_context;
 		/* Now the unnamed statement is complete and valid */
@@ -1393,7 +1401,7 @@ exec_bind_message(StringInfo input_message)
 	 * functions.
 	 */
 	if (IsAbortedTransactionBlockState() &&
-		(!IsTransactionExitStmtList(pstmt->query_list) ||
+		(!IsTransactionExitStmtList(pstmt->stmt_list) ||
 		 numParams != 0))
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
@@ -1581,22 +1589,21 @@ exec_bind_message(StringInfo input_message)
 	 * portal's queryContext becomes its own heap context rather than the
 	 * prepared statement's context.  FIXME someday
 	 */
-	if (pstmt->plan_list == NIL && pstmt->query_list != NIL)
+	if (pstmt->fully_planned)
+	{
+		plan_list = pstmt->stmt_list;
+		qContext = pstmt->context;
+	}
+	else
 	{
 		MemoryContext oldContext;
 
 		qContext = PortalGetHeapMemory(portal);
 		oldContext = MemoryContextSwitchTo(qContext);
-		query_list = copyObject(pstmt->query_list);
+		query_list = copyObject(pstmt->stmt_list);
 		plan_list = pg_plan_queries(query_list, params, true);
 		MemoryContextSwitchTo(oldContext);
 	}
-	else
-	{
-		query_list = pstmt->query_list;
-		plan_list = pstmt->plan_list;
-		qContext = pstmt->context;
-	}
 
 	/*
 	 * Define portal and start execution.
@@ -1605,7 +1612,6 @@ exec_bind_message(StringInfo input_message)
 					  *pstmt->stmt_name ? pstmt->stmt_name : NULL,
 					  pstmt->query_string,
 					  pstmt->commandTag,
-					  query_list,
 					  plan_list,
 					  qContext);
 
@@ -1688,13 +1694,13 @@ exec_execute_message(const char *portal_name, long max_rows)
 	 */
 	if (portal->commandTag == NULL)
 	{
-		Assert(portal->parseTrees == NIL);
+		Assert(portal->stmts == NIL);
 		NullCommand(dest);
 		return;
 	}
 
 	/* Does the portal contain a transaction command? */
-	is_xact_command = IsTransactionStmtList(portal->parseTrees);
+	is_xact_command = IsTransactionStmtList(portal->stmts);
 
 	/*
 	 * We must copy the sourceText and prepStmtName into MessageContext in
@@ -1760,7 +1766,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 	execute_is_fetch = !portal->atStart;
 
 	/* Log immediately if dictated by log_statement */
-	if (check_log_statement_cooked(portal->parseTrees))
+	if (check_log_statement(portal->stmts))
 	{
 		ereport(LOG,
 				(errmsg("%s %s%s%s%s%s",
@@ -1781,7 +1787,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 	 * actually run are those containing COMMIT or ROLLBACK commands.
 	 */
 	if (IsAbortedTransactionBlockState() &&
-		!IsTransactionExitStmtList(portal->parseTrees))
+		!IsTransactionExitStmtList(portal->stmts))
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 				 errmsg("current transaction is aborted, "
@@ -1865,15 +1871,16 @@ exec_execute_message(const char *portal_name, long max_rows)
 }
 
 /*
- * check_log_statement_raw
+ * check_log_statement
  *		Determine whether command should be logged because of log_statement
  *
- * raw_parsetree_list is the raw grammar output
+ * parsetree_list can be either raw grammar output or a list of planned
+ * statements
  */
 static bool
-check_log_statement_raw(List *raw_parsetree_list)
+check_log_statement(List *stmt_list)
 {
-	ListCell   *parsetree_item;
+	ListCell   *stmt_item;
 
 	if (log_statement == LOGSTMT_NONE)
 		return false;
@@ -1881,37 +1888,11 @@ check_log_statement_raw(List *raw_parsetree_list)
 		return true;
 
 	/* Else we have to inspect the statement(s) to see whether to log */
-	foreach(parsetree_item, raw_parsetree_list)
+	foreach(stmt_item, stmt_list)
 	{
-		Node	   *parsetree = (Node *) lfirst(parsetree_item);
+		Node	   *stmt = (Node *) lfirst(stmt_item);
 
-		if (GetCommandLogLevel(parsetree) <= log_statement)
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * check_log_statement_cooked
- *		As above, but work from already-analyzed querytrees
- */
-static bool
-check_log_statement_cooked(List *parsetree_list)
-{
-	ListCell   *parsetree_item;
-
-	if (log_statement == LOGSTMT_NONE)
-		return false;
-	if (log_statement == LOGSTMT_ALL)
-		return true;
-
-	/* Else we have to inspect the statement(s) to see whether to log */
-	foreach(parsetree_item, parsetree_list)
-	{
-		Query	   *parsetree = (Query *) lfirst(parsetree_item);
-
-		if (GetQueryLogLevel(parsetree) <= log_statement)
+		if (GetCommandLogLevel(stmt) <= log_statement)
 			return true;
 	}
 
@@ -2259,6 +2240,7 @@ finish_xact_command(void)
  * ones that we allow in transaction-aborted state.
  */
 
+/* Test a bare parsetree */
 static bool
 IsTransactionExitStmt(Node *parsetree)
 {
@@ -2275,29 +2257,45 @@ IsTransactionExitStmt(Node *parsetree)
 	return false;
 }
 
+/* Test a list that might contain Query nodes or bare parsetrees */
 static bool
 IsTransactionExitStmtList(List *parseTrees)
 {
 	if (list_length(parseTrees) == 1)
 	{
-		Query	   *query = (Query *) linitial(parseTrees);
+		Node	   *stmt = (Node *) linitial(parseTrees);
+
+		if (IsA(stmt, Query))
+		{
+			Query	   *query = (Query *) stmt;
 
-		if (query->commandType == CMD_UTILITY &&
-			IsTransactionExitStmt(query->utilityStmt))
+			if (query->commandType == CMD_UTILITY &&
+				IsTransactionExitStmt(query->utilityStmt))
+				return true;
+		}
+		else if (IsTransactionExitStmt(stmt))
 			return true;
 	}
 	return false;
 }
 
+/* Test a list that might contain Query nodes or bare parsetrees */
 static bool
 IsTransactionStmtList(List *parseTrees)
 {
 	if (list_length(parseTrees) == 1)
 	{
-		Query	   *query = (Query *) linitial(parseTrees);
+		Node	   *stmt = (Node *) linitial(parseTrees);
 
-		if (query->commandType == CMD_UTILITY &&
-			query->utilityStmt && IsA(query->utilityStmt, TransactionStmt))
+		if (IsA(stmt, Query))
+		{
+			Query	   *query = (Query *) stmt;
+
+			if (query->commandType == CMD_UTILITY &&
+				IsA(query->utilityStmt, TransactionStmt))
+				return true;
+		}
+		else if (IsA(stmt, TransactionStmt))
 			return true;
 	}
 	return false;
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index f6f157e0cbb..97a003ac89d 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.113 2007/02/18 19:49:25 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.114 2007/02/20 17:32:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,8 +32,7 @@
 Portal		ActivePortal = NULL;
 
 
-static void ProcessQuery(Query *parsetree,
-			 Plan *plan,
+static void ProcessQuery(PlannedStmt *plan,
 			 ParamListInfo params,
 			 DestReceiver *dest,
 			 char *completionTag);
@@ -42,7 +41,7 @@ static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
 			 DestReceiver *dest);
 static long PortalRunSelect(Portal portal, bool forward, long count,
 				DestReceiver *dest);
-static void PortalRunUtility(Portal portal, Query *query,
+static void PortalRunUtility(Portal portal, Node *utilityStmt,
 				 DestReceiver *dest, char *completionTag);
 static void PortalRunMulti(Portal portal,
 			   DestReceiver *dest, DestReceiver *altdest,
@@ -58,8 +57,7 @@ static void DoPortalRewind(Portal portal);
  * CreateQueryDesc
  */
 QueryDesc *
-CreateQueryDesc(Query *parsetree,
-				Plan *plantree,
+CreateQueryDesc(PlannedStmt *plannedstmt,
 				Snapshot snapshot,
 				Snapshot crosscheck_snapshot,
 				DestReceiver *dest,
@@ -68,10 +66,10 @@ CreateQueryDesc(Query *parsetree,
 {
 	QueryDesc  *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
 
-	qd->operation = parsetree->commandType;		/* operation */
-	qd->parsetree = parsetree;	/* parse tree */
-	qd->plantree = plantree;	/* plan */
-	qd->snapshot = snapshot;	/* snapshot */
+	qd->operation = plannedstmt->commandType;	/* operation */
+	qd->plannedstmt = plannedstmt;				/* plan */
+	qd->utilitystmt = NULL;
+	qd->snapshot = snapshot;					/* snapshot */
 	qd->crosscheck_snapshot = crosscheck_snapshot;		/* RI check snapshot */
 	qd->dest = dest;			/* output dest */
 	qd->params = params;		/* parameter values passed into query */
@@ -85,6 +83,34 @@ CreateQueryDesc(Query *parsetree,
 	return qd;
 }
 
+/*
+ * CreateUtilityQueryDesc
+ */
+QueryDesc *
+CreateUtilityQueryDesc(Node *utilitystmt,
+					   Snapshot snapshot,
+					   DestReceiver *dest,
+					   ParamListInfo params)
+{
+	QueryDesc  *qd = (QueryDesc *) palloc(sizeof(QueryDesc));
+
+	qd->operation = CMD_UTILITY;				/* operation */
+	qd->plannedstmt = NULL;
+	qd->utilitystmt = utilitystmt;				/* utility command */
+	qd->snapshot = snapshot;					/* snapshot */
+	qd->crosscheck_snapshot = InvalidSnapshot;	/* RI check snapshot */
+	qd->dest = dest;			/* output dest */
+	qd->params = params;		/* parameter values passed into query */
+	qd->doInstrument = false;	/* uninteresting for utilities */
+
+	/* null these fields until set by ExecutorStart */
+	qd->tupDesc = NULL;
+	qd->estate = NULL;
+	qd->planstate = NULL;
+
+	return qd;
+}
+
 /*
  * FreeQueryDesc
  */
@@ -103,7 +129,6 @@ FreeQueryDesc(QueryDesc *qdesc)
  *		Execute a single plannable query within a PORTAL_MULTI_QUERY
  *		or PORTAL_ONE_RETURNING portal
  *
- *	parsetree: the query tree
  *	plan: the plan tree for the query
  *	params: any parameters needed
  *	dest: where to send results
@@ -116,13 +141,11 @@ FreeQueryDesc(QueryDesc *qdesc)
  * error; otherwise the executor's memory usage will be leaked.
  */
 static void
-ProcessQuery(Query *parsetree,
-			 Plan *plan,
+ProcessQuery(PlannedStmt *plan,
 			 ParamListInfo params,
 			 DestReceiver *dest,
 			 char *completionTag)
 {
-	int			operation = parsetree->commandType;
 	QueryDesc  *queryDesc;
 
 	ereport(DEBUG3,
@@ -137,7 +160,7 @@ ProcessQuery(Query *parsetree,
 	/*
 	 * Create the QueryDesc object
 	 */
-	queryDesc = CreateQueryDesc(parsetree, plan,
+	queryDesc = CreateQueryDesc(plan,
 								ActiveSnapshot, InvalidSnapshot,
 								dest, params, false);
 
@@ -163,7 +186,7 @@ ProcessQuery(Query *parsetree,
 	{
 		Oid			lastOid;
 
-		switch (operation)
+		switch (queryDesc->operation)
 		{
 			case CMD_SELECT:
 				strcpy(completionTag, "SELECT");
@@ -206,40 +229,67 @@ ProcessQuery(Query *parsetree,
 
 /*
  * ChoosePortalStrategy
- *		Select portal execution strategy given the intended query list.
+ *		Select portal execution strategy given the intended statement list.
+ *
+ * The list elements can be Querys, PlannedStmts, or utility statements.
+ * That's more general than portals need, but we use this for prepared
+ * statements as well.
  *
  * See the comments in portal.h.
  */
 PortalStrategy
-ChoosePortalStrategy(List *parseTrees)
+ChoosePortalStrategy(List *stmts)
 {
 	int			nSetTag;
 	ListCell   *lc;
 
 	/*
 	 * PORTAL_ONE_SELECT and PORTAL_UTIL_SELECT need only consider the
-	 * single-Query-struct case, since there are no rewrite rules that can add
+	 * single-statement case, since there are no rewrite rules that can add
 	 * auxiliary queries to a SELECT or a utility command.
 	 */
-	if (list_length(parseTrees) == 1)
+	if (list_length(stmts) == 1)
 	{
-		Query	   *query = (Query *) linitial(parseTrees);
+		Node	   *stmt = (Node *) linitial(stmts);
 
-		Assert(IsA(query, Query));
-		if (query->canSetTag)
+		if (IsA(stmt, Query))
 		{
-			if (query->commandType == CMD_SELECT &&
-				query->into == NULL)
-				return PORTAL_ONE_SELECT;
-			if (query->commandType == CMD_UTILITY &&
-				query->utilityStmt != NULL)
+			Query	   *query = (Query *) stmt;
+
+			if (query->canSetTag)
 			{
-				if (UtilityReturnsTuples(query->utilityStmt))
-					return PORTAL_UTIL_SELECT;
-				/* it can't be ONE_RETURNING, so give up */
-				return PORTAL_MULTI_QUERY;
+				if (query->commandType == CMD_SELECT &&
+					query->into == NULL)
+					return PORTAL_ONE_SELECT;
+				if (query->commandType == CMD_UTILITY &&
+					query->utilityStmt != NULL)
+				{
+					if (UtilityReturnsTuples(query->utilityStmt))
+						return PORTAL_UTIL_SELECT;
+					/* it can't be ONE_RETURNING, so give up */
+					return PORTAL_MULTI_QUERY;
+				}
 			}
 		}
+		else if (IsA(stmt, PlannedStmt))
+		{
+			PlannedStmt *pstmt = (PlannedStmt *) stmt;
+
+			if (pstmt->canSetTag)
+			{
+				if (pstmt->commandType == CMD_SELECT &&
+					pstmt->into == NULL)
+					return PORTAL_ONE_SELECT;
+			}
+		}
+		else
+		{
+			/* must be a utility command; assume it's canSetTag */
+			if (UtilityReturnsTuples(stmt))
+				return PORTAL_UTIL_SELECT;
+			/* it can't be ONE_RETURNING, so give up */
+			return PORTAL_MULTI_QUERY;
+		}
 	}
 
 	/*
@@ -248,18 +298,35 @@ ChoosePortalStrategy(List *parseTrees)
 	 * it has a RETURNING list.
 	 */
 	nSetTag = 0;
-	foreach(lc, parseTrees)
+	foreach(lc, stmts)
 	{
-		Query	   *query = (Query *) lfirst(lc);
+		Node	   *stmt = (Node *) lfirst(lc);
 
-		Assert(IsA(query, Query));
-		if (query->canSetTag)
+		if (IsA(stmt, Query))
 		{
-			if (++nSetTag > 1)
-				return PORTAL_MULTI_QUERY;		/* no need to look further */
-			if (query->returningList == NIL)
-				return PORTAL_MULTI_QUERY;		/* no need to look further */
+			Query	   *query = (Query *) stmt;
+
+			if (query->canSetTag)
+			{
+				if (++nSetTag > 1)
+					return PORTAL_MULTI_QUERY;	/* no need to look further */
+				if (query->returningList == NIL)
+					return PORTAL_MULTI_QUERY;	/* no need to look further */
+			}
 		}
+		else if (IsA(stmt, PlannedStmt))
+		{
+			PlannedStmt *pstmt = (PlannedStmt *) stmt;
+
+			if (pstmt->canSetTag)
+			{
+				if (++nSetTag > 1)
+					return PORTAL_MULTI_QUERY;	/* no need to look further */
+				if (pstmt->returningLists == NIL)
+					return PORTAL_MULTI_QUERY;	/* no need to look further */
+			}
+		}
+		/* otherwise, utility command, assumed not canSetTag */
 	}
 	if (nSetTag == 1)
 		return PORTAL_ONE_RETURNING;
@@ -274,48 +341,84 @@ ChoosePortalStrategy(List *parseTrees)
  *		Returns NIL if the portal doesn't have a determinable targetlist.
  *
  * Note: do not modify the result.
- *
- * XXX be careful to keep this in sync with FetchPreparedStatementTargetList,
- * and with UtilityReturnsTuples.
  */
 List *
 FetchPortalTargetList(Portal portal)
 {
-	if (portal->strategy == PORTAL_ONE_SELECT)
-		return ((Query *) linitial(portal->parseTrees))->targetList;
-	if (portal->strategy == PORTAL_ONE_RETURNING)
-		return (PortalGetPrimaryQuery(portal))->returningList;
-	if (portal->strategy == PORTAL_UTIL_SELECT)
+	/* no point in looking if we determined it doesn't return tuples */
+	if (portal->strategy == PORTAL_MULTI_QUERY)
+		return NIL;
+	/* get the primary statement and find out what it returns */
+	return FetchStatementTargetList(PortalGetPrimaryStmt(portal));
+}
+
+/*
+ * FetchStatementTargetList
+ *		Given a statement that returns tuples, extract the query targetlist.
+ *		Returns NIL if the statement doesn't have a determinable targetlist.
+ *
+ * This can be applied to a Query, a PlannedStmt, or a utility statement.
+ * That's more general than portals need, but we use this for prepared
+ * statements as well.
+ *
+ * Note: do not modify the result.
+ *
+ * XXX be careful to keep this in sync with UtilityReturnsTuples.
+ */
+List *
+FetchStatementTargetList(Node *stmt)
+{
+	if (stmt == NULL)
+		return NIL;
+	if (IsA(stmt, Query))
 	{
-		Node	   *utilityStmt;
+		Query	   *query = (Query *) stmt;
 
-		utilityStmt = ((Query *) linitial(portal->parseTrees))->utilityStmt;
-		switch (nodeTag(utilityStmt))
+		if (query->commandType == CMD_UTILITY &&
+			query->utilityStmt != NULL)
 		{
-			case T_FetchStmt:
-				{
-					FetchStmt  *substmt = (FetchStmt *) utilityStmt;
-					Portal		subportal;
-
-					Assert(!substmt->ismove);
-					subportal = GetPortalByName(substmt->portalname);
-					Assert(PortalIsValid(subportal));
-					return FetchPortalTargetList(subportal);
-				}
-
-			case T_ExecuteStmt:
-				{
-					ExecuteStmt *substmt = (ExecuteStmt *) utilityStmt;
-					PreparedStatement *entry;
+			/* transfer attention to utility statement */
+			stmt = query->utilityStmt;
+		}
+		else
+		{
+			if (query->commandType == CMD_SELECT &&
+				query->into == NULL)
+				return query->targetList;
+			if (query->returningList)
+				return query->returningList;
+			return NIL;
+		}
+	}
+	if (IsA(stmt, PlannedStmt))
+	{
+		PlannedStmt *pstmt = (PlannedStmt *) stmt;
+
+		if (pstmt->commandType == CMD_SELECT &&
+			pstmt->into == NULL)
+			return pstmt->planTree->targetlist;
+		if (pstmt->returningLists)
+			return (List *) linitial(pstmt->returningLists);
+		return NIL;
+	}
+	if (IsA(stmt, FetchStmt))
+	{
+		FetchStmt  *fstmt = (FetchStmt *) stmt;
+		Portal		subportal;
 
-					Assert(!substmt->into);
-					entry = FetchPreparedStatement(substmt->name, true);
-					return FetchPreparedStatementTargetList(entry);
-				}
+		Assert(!fstmt->ismove);
+		subportal = GetPortalByName(fstmt->portalname);
+		Assert(PortalIsValid(subportal));
+		return FetchPortalTargetList(subportal);
+	}
+	if (IsA(stmt, ExecuteStmt))
+	{
+		ExecuteStmt *estmt = (ExecuteStmt *) stmt;
+		PreparedStatement *entry;
 
-			default:
-				break;
-		}
+		Assert(!estmt->into);
+		entry = FetchPreparedStatement(estmt->name, true);
+		return FetchPreparedStatementTargetList(entry);
 	}
 	return NIL;
 }
@@ -374,7 +477,7 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
 		/*
 		 * Determine the portal execution strategy
 		 */
-		portal->strategy = ChoosePortalStrategy(portal->parseTrees);
+		portal->strategy = ChoosePortalStrategy(portal->stmts);
 
 		/*
 		 * Fire her up according to the strategy
@@ -396,8 +499,7 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
 				 * Create QueryDesc in portal's context; for the moment, set
 				 * the destination to DestNone.
 				 */
-				queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees),
-										(Plan *) linitial(portal->planTrees),
+				queryDesc = CreateQueryDesc((PlannedStmt *) linitial(portal->stmts),
 											ActiveSnapshot,
 											InvalidSnapshot,
 											None_Receiver,
@@ -450,8 +552,16 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
 				 * We don't start the executor until we are told to run the
 				 * portal.	We do need to set up the result tupdesc.
 				 */
-				portal->tupDesc =
-					ExecCleanTypeFromTL((PortalGetPrimaryQuery(portal))->returningList, false);
+				{
+					PlannedStmt *pstmt;
+
+					pstmt = (PlannedStmt *) PortalGetPrimaryStmt(portal);
+					Assert(IsA(pstmt, PlannedStmt));
+					Assert(pstmt->returningLists);
+					portal->tupDesc =
+						ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists),
+											false);
+				}
 
 				/*
 				 * Reset cursor position data to "start of query"
@@ -468,8 +578,12 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
 				 * We don't set snapshot here, because PortalRunUtility will
 				 * take care of it if needed.
 				 */
-				portal->tupDesc =
-					UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt);
+				{
+					Node *ustmt = PortalGetPrimaryStmt(portal);
+
+					Assert(!IsA(ustmt, PlannedStmt));
+					portal->tupDesc = UtilityTupleDescriptor(ustmt);
+				}
 
 				/*
 				 * Reset cursor position data to "start of query"
@@ -934,7 +1048,7 @@ FillPortalStore(Portal portal)
 			break;
 
 		case PORTAL_UTIL_SELECT:
-			PortalRunUtility(portal, linitial(portal->parseTrees),
+			PortalRunUtility(portal, (Node *) linitial(portal->stmts),
 							 treceiver, completionTag);
 			break;
 
@@ -1023,11 +1137,9 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
  *		Execute a utility statement inside a portal.
  */
 static void
-PortalRunUtility(Portal portal, Query *query,
+PortalRunUtility(Portal portal, Node *utilityStmt,
 				 DestReceiver *dest, char *completionTag)
 {
-	Node	   *utilityStmt = query->utilityStmt;
-
 	ereport(DEBUG3,
 			(errmsg_internal("ProcessUtility")));
 
@@ -1061,18 +1173,7 @@ PortalRunUtility(Portal portal, Query *query,
 	else
 		ActiveSnapshot = NULL;
 
-	if (query->canSetTag)
-	{
-		/* utility statement can override default tag string */
-		ProcessUtility(utilityStmt, portal->portalParams, dest, completionTag);
-		if (completionTag && completionTag[0] == '\0' && portal->commandTag)
-			strcpy(completionTag, portal->commandTag);	/* use the default */
-	}
-	else
-	{
-		/* utility added by rewrite cannot set tag */
-		ProcessUtility(utilityStmt, portal->portalParams, dest, NULL);
-	}
+	ProcessUtility(utilityStmt, portal->portalParams, dest, completionTag);
 
 	/* Some utility statements may change context on us */
 	MemoryContextSwitchTo(PortalGetHeapMemory(portal));
@@ -1092,8 +1193,7 @@ PortalRunMulti(Portal portal,
 			   DestReceiver *dest, DestReceiver *altdest,
 			   char *completionTag)
 {
-	ListCell   *querylist_item;
-	ListCell   *planlist_item;
+	ListCell   *stmtlist_item;
 
 	/*
 	 * If the destination is DestRemoteExecute, change to DestNone.  The
@@ -1114,47 +1214,36 @@ PortalRunMulti(Portal portal,
 	 * Loop to handle the individual queries generated from a single parsetree
 	 * by analysis and rewrite.
 	 */
-	forboth(querylist_item, portal->parseTrees,
-			planlist_item, portal->planTrees)
+	foreach(stmtlist_item, portal->stmts)
 	{
-		Query	   *query = (Query *) lfirst(querylist_item);
-		Plan	   *plan = (Plan *) lfirst(planlist_item);
+		Node   *stmt = (Node *) lfirst(stmtlist_item);
 
 		/*
 		 * If we got a cancel signal in prior command, quit
 		 */
 		CHECK_FOR_INTERRUPTS();
 
-		if (query->commandType == CMD_UTILITY)
-		{
-			/*
-			 * process utility functions (create, destroy, etc..)
-			 */
-			Assert(plan == NULL);
-
-			PortalRunUtility(portal, query,
-							 query->canSetTag ? dest : altdest,
-							 completionTag);
-		}
-		else
+		if (IsA(stmt, PlannedStmt))
 		{
 			/*
 			 * process a plannable query.
 			 */
+			PlannedStmt *pstmt = (PlannedStmt *) stmt;
+
 			if (log_executor_stats)
 				ResetUsage();
 
-			if (query->canSetTag)
+			if (pstmt->canSetTag)
 			{
 				/* statement can set tag string */
-				ProcessQuery(query, plan,
+				ProcessQuery(pstmt,
 							 portal->portalParams,
 							 dest, completionTag);
 			}
 			else
 			{
 				/* stmt added by rewrite cannot set tag */
-				ProcessQuery(query, plan,
+				ProcessQuery(pstmt,
 							 portal->portalParams,
 							 altdest, NULL);
 			}
@@ -1162,12 +1251,25 @@ PortalRunMulti(Portal portal,
 			if (log_executor_stats)
 				ShowUsage("EXECUTOR STATISTICS");
 		}
+		else
+		{
+			/*
+			 * process utility functions (create, destroy, etc..)
+			 *
+			 * These are assumed canSetTag if they're the only stmt in the
+			 * portal.
+			 */
+			if (list_length(portal->stmts) == 1)
+				PortalRunUtility(portal, stmt, dest, completionTag);
+			else
+				PortalRunUtility(portal, stmt, altdest, NULL);
+		}
 
 		/*
 		 * Increment command counter between queries, but not after the last
 		 * one.
 		 */
-		if (lnext(planlist_item) != NULL)
+		if (lnext(stmtlist_item) != NULL)
 			CommandCounterIncrement();
 
 		/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5cabec2970b..47051ad1ed2 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.272 2007/02/14 01:58:57 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.273 2007/02/20 17:32:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -248,36 +248,41 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
 
 
 /*
- * QueryIsReadOnly: is an analyzed/rewritten query read-only?
+ * CommandIsReadOnly: is an executable query read-only?
  *
  * This is a much stricter test than we apply for XactReadOnly mode;
  * the query must be *in truth* read-only, because the caller wishes
  * not to do CommandCounterIncrement for it.
+ *
+ * Note: currently no need to support Query nodes here
  */
 bool
-QueryIsReadOnly(Query *parsetree)
+CommandIsReadOnly(Node *parsetree)
 {
-	switch (parsetree->commandType)
+	if (IsA(parsetree, PlannedStmt))
 	{
-		case CMD_SELECT:
-			if (parsetree->into != NULL)
-				return false;	/* SELECT INTO */
-			else if (parsetree->rowMarks != NIL)
-				return false;	/* SELECT FOR UPDATE/SHARE */
-			else
-				return true;
-		case CMD_UPDATE:
-		case CMD_INSERT:
-		case CMD_DELETE:
-			return false;
-		case CMD_UTILITY:
-			/* For now, treat all utility commands as read/write */
-			return false;
-		default:
-			elog(WARNING, "unrecognized commandType: %d",
-				 (int) parsetree->commandType);
-			break;
+		PlannedStmt *stmt = (PlannedStmt *) parsetree;
+
+		switch (stmt->commandType)
+		{
+			case CMD_SELECT:
+				if (stmt->into != NULL)
+					return false;	/* SELECT INTO */
+				else if (stmt->rowMarks != NIL)
+					return false;	/* SELECT FOR UPDATE/SHARE */
+				else
+					return true;
+			case CMD_UPDATE:
+			case CMD_INSERT:
+			case CMD_DELETE:
+				return false;
+			default:
+				elog(WARNING, "unrecognized commandType: %d",
+					 (int) stmt->commandType);
+				break;
+		}
 	}
+	/* For now, treat all utility commands as read/write */
 	return false;
 }
 
@@ -1161,7 +1166,7 @@ UtilityReturnsTuples(Node *parsetree)
 				entry = FetchPreparedStatement(stmt->name, false);
 				if (!entry)
 					return false;		/* not our business to raise error */
-				switch (ChoosePortalStrategy(entry->query_list))
+				switch (ChoosePortalStrategy(entry->stmt_list))
 				{
 					case PORTAL_ONE_SELECT:
 					case PORTAL_ONE_RETURNING:
@@ -1244,6 +1249,7 @@ UtilityTupleDescriptor(Node *parsetree)
  * QueryReturnsTuples
  *		Return "true" if this Query will send output to the destination.
  */
+#ifdef NOT_USED
 bool
 QueryReturnsTuples(Query *parsetree)
 {
@@ -1270,14 +1276,15 @@ QueryReturnsTuples(Query *parsetree)
 	}
 	return false;				/* default */
 }
+#endif
 
 
 /*
  * CreateCommandTag
- *		utility to get a string representation of the
- *		command operation, given a raw (un-analyzed) parsetree.
+ *		utility to get a string representation of the command operation,
+ *		given either a raw (un-analyzed) parsetree or a planned query.
  *
- * This must handle all raw command types, but since the vast majority
+ * This must handle all command types, but since the vast majority
  * of 'em are utility commands, it seems sensible to keep it here.
  *
  * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
@@ -1290,6 +1297,7 @@ CreateCommandTag(Node *parsetree)
 
 	switch (nodeTag(parsetree))
 	{
+		/* raw plannable queries */
 		case T_InsertStmt:
 			tag = "INSERT";
 			break;
@@ -1306,6 +1314,7 @@ CreateCommandTag(Node *parsetree)
 			tag = "SELECT";
 			break;
 
+		/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			{
 				TransactionStmt *stmt = (TransactionStmt *) parsetree;
@@ -1826,65 +1835,98 @@ CreateCommandTag(Node *parsetree)
 			tag = "DEALLOCATE";
 			break;
 
-		default:
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(parsetree));
-			tag = "???";
-			break;
-	}
-
-	return tag;
-}
-
-/*
- * CreateQueryTag
- *		utility to get a string representation of a Query operation.
- *
- * This is exactly like CreateCommandTag, except it works on a Query
- * that has already been through parse analysis (and possibly further).
- */
-const char *
-CreateQueryTag(Query *parsetree)
-{
-	const char *tag;
-
-	Assert(IsA(parsetree, Query));
+		/* already-planned queries */
+		case T_PlannedStmt:
+			{
+				PlannedStmt *stmt = (PlannedStmt *) parsetree;
 
-	switch (parsetree->commandType)
-	{
-		case CMD_SELECT:
+				switch (stmt->commandType)
+				{
+					case CMD_SELECT:
+						/*
+						 * We take a little extra care here so that the result
+						 * will be useful for complaints about read-only
+						 * statements
+						 */
+						if (stmt->into != NULL)
+							tag = "SELECT INTO";
+						else if (stmt->rowMarks != NIL)
+						{
+							if (((RowMarkClause *) linitial(stmt->rowMarks))->forUpdate)
+								tag = "SELECT FOR UPDATE";
+							else
+								tag = "SELECT FOR SHARE";
+						}
+						else
+							tag = "SELECT";
+						break;
+					case CMD_UPDATE:
+						tag = "UPDATE";
+						break;
+					case CMD_INSERT:
+						tag = "INSERT";
+						break;
+					case CMD_DELETE:
+						tag = "DELETE";
+						break;
+					default:
+						elog(WARNING, "unrecognized commandType: %d",
+							 (int) stmt->commandType);
+						tag = "???";
+						break;
+				}
+			}
+			break;
 
-			/*
-			 * We take a little extra care here so that the result will be
-			 * useful for complaints about read-only statements
-			 */
-			if (parsetree->into != NULL)
-				tag = "SELECT INTO";
-			else if (parsetree->rowMarks != NIL)
+		/* parsed-and-rewritten-but-not-planned queries */
+		case T_Query:
 			{
-				if (((RowMarkClause *) linitial(parsetree->rowMarks))->forUpdate)
-					tag = "SELECT FOR UPDATE";
-				else
-					tag = "SELECT FOR SHARE";
+				Query *stmt = (Query *) parsetree;
+
+				switch (stmt->commandType)
+				{
+					case CMD_SELECT:
+						/*
+						 * We take a little extra care here so that the result
+						 * will be useful for complaints about read-only
+						 * statements
+						 */
+						if (stmt->into != NULL)
+							tag = "SELECT INTO";
+						else if (stmt->rowMarks != NIL)
+						{
+							if (((RowMarkClause *) linitial(stmt->rowMarks))->forUpdate)
+								tag = "SELECT FOR UPDATE";
+							else
+								tag = "SELECT FOR SHARE";
+						}
+						else
+							tag = "SELECT";
+						break;
+					case CMD_UPDATE:
+						tag = "UPDATE";
+						break;
+					case CMD_INSERT:
+						tag = "INSERT";
+						break;
+					case CMD_DELETE:
+						tag = "DELETE";
+						break;
+					case CMD_UTILITY:
+						tag = CreateCommandTag(stmt->utilityStmt);
+						break;
+					default:
+						elog(WARNING, "unrecognized commandType: %d",
+							 (int) stmt->commandType);
+						tag = "???";
+						break;
+				}
 			}
-			else
-				tag = "SELECT";
-			break;
-		case CMD_UPDATE:
-			tag = "UPDATE";
-			break;
-		case CMD_INSERT:
-			tag = "INSERT";
-			break;
-		case CMD_DELETE:
-			tag = "DELETE";
-			break;
-		case CMD_UTILITY:
-			tag = CreateCommandTag(parsetree->utilityStmt);
 			break;
+
 		default:
-			elog(WARNING, "unrecognized commandType: %d",
-				 (int) parsetree->commandType);
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
 			tag = "???";
 			break;
 	}
@@ -1896,9 +1938,9 @@ CreateQueryTag(Query *parsetree)
 /*
  * GetCommandLogLevel
  *		utility to get the minimum log_statement level for a command,
- *		given a raw (un-analyzed) parsetree.
+ *		given either a raw (un-analyzed) parsetree or a planned query.
  *
- * This must handle all raw command types, but since the vast majority
+ * This must handle all command types, but since the vast majority
  * of 'em are utility commands, it seems sensible to keep it here.
  */
 LogStmtLevel
@@ -1908,6 +1950,7 @@ GetCommandLogLevel(Node *parsetree)
 
 	switch (nodeTag(parsetree))
 	{
+		/* raw plannable queries */
 		case T_InsertStmt:
 		case T_DeleteStmt:
 		case T_UpdateStmt:
@@ -1921,6 +1964,7 @@ GetCommandLogLevel(Node *parsetree)
 				lev = LOGSTMT_ALL;
 			break;
 
+		/* utility statements --- same whether raw or cooked */
 		case T_TransactionStmt:
 			lev = LOGSTMT_ALL;
 			break;
@@ -2216,12 +2260,12 @@ GetCommandLogLevel(Node *parsetree)
 				pstmt = FetchPreparedStatement(stmt->name, false);
 				if (pstmt)
 				{
-					foreach(l, pstmt->query_list)
+					foreach(l, pstmt->stmt_list)
 					{
-						Query	   *query = (Query *) lfirst(l);
+						Node	   *substmt = (Node *) lfirst(l);
 						LogStmtLevel stmt_lev;
 
-						stmt_lev = GetQueryLogLevel(query);
+						stmt_lev = GetCommandLogLevel(substmt);
 						lev = Min(lev, stmt_lev);
 					}
 				}
@@ -2232,62 +2276,72 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_ALL;
 			break;
 
-		case T_Query:
+		/* already-planned queries */
+		case T_PlannedStmt:
+			{
+				PlannedStmt *stmt = (PlannedStmt *) parsetree;
 
-			/*
-			 * In complicated situations (eg, EXPLAIN ANALYZE in an extended
-			 * Query protocol), we might find an already-analyzed query within
-			 * a utility statement.  Cope.
-			 */
-			lev = GetQueryLogLevel((Query *) parsetree);
-			break;
+				switch (stmt->commandType)
+				{
+					case CMD_SELECT:
+						if (stmt->into != NULL)
+							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
+						else
+							lev = LOGSTMT_ALL;
+						break;
 
-		default:
-			elog(WARNING, "unrecognized node type: %d",
-				 (int) nodeTag(parsetree));
-			lev = LOGSTMT_ALL;
+					case CMD_UPDATE:
+					case CMD_INSERT:
+					case CMD_DELETE:
+						lev = LOGSTMT_MOD;
+						break;
+
+					default:
+						elog(WARNING, "unrecognized commandType: %d",
+							 (int) stmt->commandType);
+						lev = LOGSTMT_ALL;
+						break;
+				}
+			}
 			break;
-	}
 
-	return lev;
-}
+		/* parsed-and-rewritten-but-not-planned queries */
+		case T_Query:
+			{
+				Query *stmt = (Query *) parsetree;
 
-/*
- * GetQueryLogLevel
- *		utility to get the minimum log_statement level for a Query operation.
- *
- * This is exactly like GetCommandLogLevel, except it works on a Query
- * that has already been through parse analysis (and possibly further).
- */
-LogStmtLevel
-GetQueryLogLevel(Query *parsetree)
-{
-	LogStmtLevel lev;
+				switch (stmt->commandType)
+				{
+					case CMD_SELECT:
+						if (stmt->into != NULL)
+							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
+						else
+							lev = LOGSTMT_ALL;
+						break;
 
-	Assert(IsA(parsetree, Query));
+					case CMD_UPDATE:
+					case CMD_INSERT:
+					case CMD_DELETE:
+						lev = LOGSTMT_MOD;
+						break;
 
-	switch (parsetree->commandType)
-	{
-		case CMD_SELECT:
-			if (parsetree->into != NULL)
-				lev = LOGSTMT_DDL;		/* CREATE AS, SELECT INTO */
-			else
-				lev = LOGSTMT_ALL;
-			break;
+					case CMD_UTILITY:
+						lev = GetCommandLogLevel(stmt->utilityStmt);
+						break;
 
-		case CMD_UPDATE:
-		case CMD_INSERT:
-		case CMD_DELETE:
-			lev = LOGSTMT_MOD;
-			break;
+					default:
+						elog(WARNING, "unrecognized commandType: %d",
+							 (int) stmt->commandType);
+						lev = LOGSTMT_ALL;
+						break;
+				}
 
-		case CMD_UTILITY:
-			lev = GetCommandLogLevel(parsetree->utilityStmt);
+			}
 			break;
 
 		default:
-			elog(WARNING, "unrecognized commandType: %d",
-				 (int) parsetree->commandType);
+			elog(WARNING, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
 			lev = LOGSTMT_ALL;
 			break;
 	}
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 8c9d58422ef..3bd2ee6397f 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.98 2007/01/05 22:19:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.99 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -141,29 +141,45 @@ GetPortalByName(const char *name)
 }
 
 /*
- * PortalListGetPrimaryQuery
- *		Get the "primary" Query within a portal, ie, the one marked canSetTag.
+ * PortalListGetPrimaryStmt
+ *		Get the "primary" stmt within a portal, ie, the one marked canSetTag.
  *
- * Returns NULL if no such Query.  If multiple Query structs within the
+ * Returns NULL if no such stmt.  If multiple PlannedStmt structs within the
  * portal are marked canSetTag, returns the first one.	Neither of these
  * cases should occur in present usages of this function.
  *
+ * Copes if given a list of Querys --- can't happen in a portal, but this
+ * code also supports prepared statements, which need both cases.
+ *
  * Note: the reason this is just handed a List is so that prepared statements
- * can share the code.	For use with a portal, use PortalGetPrimaryQuery
+ * can share the code.	For use with a portal, use PortalGetPrimaryStmt
  * rather than calling this directly.
  */
-Query *
-PortalListGetPrimaryQuery(List *parseTrees)
+Node *
+PortalListGetPrimaryStmt(List *stmts)
 {
 	ListCell   *lc;
 
-	foreach(lc, parseTrees)
+	foreach(lc, stmts)
 	{
-		Query	   *query = (Query *) lfirst(lc);
+		Node   *stmt = (Node *) lfirst(lc);
 
-		Assert(IsA(query, Query));
-		if (query->canSetTag)
-			return query;
+		if (IsA(stmt, PlannedStmt))
+		{
+			if (((PlannedStmt *) stmt)->canSetTag)
+				return stmt;
+		}
+		else if (IsA(stmt, Query))
+		{
+			if (((Query *) stmt)->canSetTag)
+				return stmt;
+		}
+		else
+		{
+			/* Utility stmts are assumed canSetTag if they're the only stmt */
+			if (list_length(stmts) == 1)
+				return stmt;
+		}
 	}
 	return NULL;
 }
@@ -261,30 +277,25 @@ CreateNewPortal(void)
  * (before rewriting) was an empty string.	Also, the passed commandTag must
  * be a pointer to a constant string, since it is not copied.  The caller is
  * responsible for ensuring that the passed prepStmtName (if any), sourceText
- * (if any), parse and plan trees have adequate lifetime.  Also, queryContext
- * must accurately describe the location of the parse trees.
+ * (if any), and plan trees have adequate lifetime.
  */
 void
 PortalDefineQuery(Portal portal,
 				  const char *prepStmtName,
 				  const char *sourceText,
 				  const char *commandTag,
-				  List *parseTrees,
-				  List *planTrees,
+				  List *stmts,
 				  MemoryContext queryContext)
 {
 	AssertArg(PortalIsValid(portal));
 	AssertState(portal->queryContext == NULL);	/* else defined already */
 
-	Assert(list_length(parseTrees) == list_length(planTrees));
-
-	Assert(commandTag != NULL || parseTrees == NIL);
+	Assert(commandTag != NULL || stmts == NIL);
 
 	portal->prepStmtName = prepStmtName;
 	portal->sourceText = sourceText;
 	portal->commandTag = commandTag;
-	portal->parseTrees = parseTrees;
-	portal->planTrees = planTrees;
+	portal->stmts = stmts;
 	portal->queryContext = queryContext;
 }
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 20d6e24e053..8c53a2df2ac 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.387 2007/02/20 10:00:25 petere Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.388 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200702201
+#define CATALOG_VERSION_NO	200702202
 
 #endif
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index 4a17ddb767a..50fe2f13284 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.h
@@ -7,13 +7,14 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.20 2007/01/05 22:19:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.21 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PORTALCMDS_H
 #define PORTALCMDS_H
 
+#include "nodes/parsenodes.h"
 #include "utils/portal.h"
 
 
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index c1ee47fe7e8..a921bf1b045 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 2002-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.23 2007/01/05 22:19:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.24 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,10 @@
 /*
  * The data structure representing a prepared statement
  *
+ * A prepared statement might be fully planned, or only parsed-and-rewritten.
+ * If fully planned, stmt_list contains PlannedStmts and/or utility statements;
+ * if not, it contains Query nodes.
+ *
  * Note: all subsidiary storage lives in the context denoted by the context
  * field.  However, the string referenced by commandTag is not subsidiary
  * storage; it is assumed to be a compile-time-constant string.  As with
@@ -31,11 +35,11 @@ typedef struct
 	char		stmt_name[NAMEDATALEN];
 	char	   *query_string;	/* text of query, or NULL */
 	const char *commandTag;		/* command tag (a constant!), or NULL */
-	List	   *query_list;		/* list of queries, rewritten */
-	List	   *plan_list;		/* list of plans */
+	List	   *stmt_list;		/* list of statement or Query nodes */
 	List	   *argtype_list;	/* list of parameter type OIDs */
+	bool		fully_planned;	/* what is in stmt_list, exactly? */
+	bool		from_sql;		/* prepared via SQL, not FE/BE protocol? */
 	TimestampTz prepare_time;	/* the time when the stmt was prepared */
-	bool		from_sql;		/* stmt prepared via SQL, not FE/BE protocol? */
 	MemoryContext context;		/* context containing this query */
 } PreparedStatement;
 
@@ -52,9 +56,9 @@ extern void ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 extern void StorePreparedStatement(const char *stmt_name,
 					   const char *query_string,
 					   const char *commandTag,
-					   List *query_list,
-					   List *plan_list,
+					   List *stmt_list,
 					   List *argtype_list,
+					   bool fully_planned,
 					   bool from_sql);
 extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
 					   bool throwError);
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index cf991125d4b..d5ae745a296 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.33 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.34 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,7 @@
 #define EXECDESC_H
 
 #include "nodes/execnodes.h"
-#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
 #include "tcop/dest.h"
 
 
@@ -24,15 +24,19 @@
  *		query descriptor:
  *
  *	a QueryDesc encapsulates everything that the executor
- *	needs to execute the query
+ *	needs to execute the query.
+ *
+ *	For the convenience of SQL-language functions, we also support QueryDescs
+ *	containing utility statements; these must not be passed to the executor
+ *	however.
  * ---------------------
  */
 typedef struct QueryDesc
 {
 	/* These fields are provided by CreateQueryDesc */
 	CmdType		operation;		/* CMD_SELECT, CMD_UPDATE, etc. */
-	Query	   *parsetree;		/* rewritten parsetree */
-	Plan	   *plantree;		/* planner's output */
+	PlannedStmt	*plannedstmt;	/* planner's output, or null if utility */
+	Node	   *utilitystmt;	/* utility statement, or null */
 	Snapshot	snapshot;		/* snapshot to use for query */
 	Snapshot	crosscheck_snapshot;	/* crosscheck for RI update/delete */
 	DestReceiver *dest;			/* the destination for tuple output */
@@ -46,13 +50,18 @@ typedef struct QueryDesc
 } QueryDesc;
 
 /* in pquery.c */
-extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
+extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt,
 				Snapshot snapshot,
 				Snapshot crosscheck_snapshot,
 				DestReceiver *dest,
 				ParamListInfo params,
 				bool doInstrument);
 
+extern QueryDesc *CreateUtilityQueryDesc(Node *utilitystmt,
+				Snapshot snapshot,
+				DestReceiver *dest,
+				ParamListInfo params);
+
 extern void FreeQueryDesc(QueryDesc *qdesc);
 
 #endif   /* EXECDESC_H  */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bfbe1ba2f38..38260b5ecd6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.136 2007/02/06 02:59:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.137 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,6 +15,7 @@
 #define EXECUTOR_H
 
 #include "executor/execdesc.h"
+#include "nodes/parsenodes.h"
 
 
 /*
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 3bfc870159f..5e65bd750ae 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.26 2007/01/05 22:19:55 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.27 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -35,10 +35,11 @@ typedef struct
 	MemoryContext plancxt;
 	/* Original query string (used for error reporting) */
 	const char *query;
-	/* List of List of querytrees; one sublist per original parsetree */
-	List	   *qtlist;
-	/* List of plan trees --- length == # of querytrees, but flat list */
-	List	   *ptlist;
+	/*
+	 * List of List of PlannedStmts and utility stmts; one sublist per
+	 * original parsetree
+	 */
+	List	   *stmt_list_list;
 	/* Argument types, if a prepared plan */
 	int			nargs;
 	Oid		   *argtypes;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 35a0ab3a607..b576f4610e3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.167 2007/02/06 02:59:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.168 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -344,7 +344,7 @@ typedef struct EState
 	ExprContext *es_per_tuple_exprcontext;
 
 	/* Below is to re-evaluate plan qual in READ COMMITTED mode */
-	Plan	   *es_topPlan;		/* link to top of plan tree */
+	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
 	struct evalPlanQual *es_evalPlanQual;		/* chain of PlanQual states */
 	bool	   *es_evTupleNull; /* local array of EPQ status */
 	HeapTuple  *es_evTuple;		/* shared array of EPQ substitute tuples */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 44175591e75..53bd13b5fbe 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.195 2007/02/19 07:03:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.196 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -141,6 +141,7 @@ typedef enum NodeTag
 	T_RangeTblRef,
 	T_JoinExpr,
 	T_FromExpr,
+	T_IntoClause,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -225,9 +226,10 @@ typedef enum NodeTag
 	T_OidList,
 
 	/*
-	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
+	 * TAGS FOR STATEMENT NODES (mostly in parsenodes.h)
 	 */
 	T_Query = 700,
+	T_PlannedStmt,
 	T_InsertStmt,
 	T_DeleteStmt,
 	T_UpdateStmt,
@@ -302,8 +304,12 @@ typedef enum NodeTag
 	T_AlterOwnerStmt,
 	T_DropOwnedStmt,
 	T_ReassignOwnedStmt,
+	T_CompositeTypeStmt,
 
-	T_A_Expr = 800,
+	/*
+	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
+	 */
+	T_A_Expr = 900,
 	T_ColumnRef,
 	T_ParamRef,
 	T_A_Const,
@@ -328,7 +334,6 @@ typedef enum NodeTag
 	T_FuncWithArgs,
 	T_PrivTarget,
 	T_CreateOpClassItem,
-	T_CompositeTypeStmt,
 	T_InhRelation,
 	T_FunctionParameter,
 	T_LockingClause,
@@ -343,7 +348,7 @@ typedef enum NodeTag
 	 * purposes (usually because they are involved in APIs where we want to
 	 * pass multiple object types through the same pointer).
 	 */
-	T_TriggerData = 900,		/* in commands/trigger.h */
+	T_TriggerData = 950,		/* in commands/trigger.h */
 	T_ReturnSetInfo,			/* in nodes/execnodes.h */
 	T_TIDBitmap					/* in nodes/tidbitmap.h */
 } NodeTag;
@@ -430,10 +435,9 @@ typedef double Cost;			/* execution cost (in page-access units) */
 
 /*
  * CmdType -
- *	  enums for type of operation represented by a Query
+ *	  enums for type of operation represented by a Query or PlannedStmt
  *
- * ??? could have put this in parsenodes.h but many files not in the
- *	  optimizer also need this...
+ * This is needed in both parsenodes.h and plannodes.h, so put it here...
  */
 typedef enum CmdType
 {
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0db72763021..ec9ccb6ce30 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.340 2007/02/03 14:06:55 petere Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.341 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,15 +27,6 @@ typedef enum QuerySource
 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
 } QuerySource;
 
-/* What to do at commit time for temporary relations */
-typedef enum OnCommitAction
-{
-	ONCOMMIT_NOOP,				/* No ON COMMIT clause (do nothing) */
-	ONCOMMIT_PRESERVE_ROWS,		/* ON COMMIT PRESERVE ROWS (do nothing) */
-	ONCOMMIT_DELETE_ROWS,		/* ON COMMIT DELETE ROWS */
-	ONCOMMIT_DROP				/* ON COMMIT DROP */
-} OnCommitAction;
-
 /* Sort ordering options for ORDER BY and CREATE INDEX */
 typedef enum SortByDir
 {
@@ -86,11 +77,14 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 
 /*
  * Query -
- *	  all statements are turned into a Query tree (via transformStmt)
- *	  for further processing by the optimizer
+ *	  Parse analysis turns all statements into a Query tree (via transformStmt)
+ *	  for further processing by the rewriter and planner.
  *
- *	  utility statements (i.e. non-optimizable statements) have the
+ *	  Utility statements (i.e. non-optimizable statements) have the
  *	  utilityStmt field set, and the Query itself is mostly dummy.
+ *
+ *	  Planning converts a Query tree into a Plan tree headed by a PlannedStmt
+ *	  noded --- the Query structure is not used by the executor.
  */
 typedef struct Query
 {
@@ -108,10 +102,7 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
-	RangeVar   *into;			/* target relation for SELECT INTO */
-	List	   *intoOptions;	/* options from WITH clause */
-	OnCommitAction intoOnCommit;	/* what do we do at COMMIT? */
-	char	   *intoTableSpaceName;		/* table space to use, or NULL */
+	IntoClause *into;			/* target for SELECT INTO / CREATE TABLE AS */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -138,29 +129,6 @@ typedef struct Query
 
 	Node	   *setOperations;	/* set-operation tree if this is top level of
 								 * a UNION/INTERSECT/EXCEPT query */
-
-	/*
-	 * If the resultRelation turns out to be the parent of an inheritance
-	 * tree, the planner will add all the child tables to the rtable and store
-	 * a list of the rtindexes of all the result relations here. This is done
-	 * at plan time, not parse time, since we don't want to commit to the
-	 * exact set of child tables at parse time.  XXX This field ought to go in
-	 * some sort of TopPlan plan node, not in the Query.
-	 */
-	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
-
-	/*
-	 * If the query has a returningList then the planner will store a list of
-	 * processed targetlists (one per result relation) here.  We must have a
-	 * separate RETURNING targetlist for each result rel because column
-	 * numbers may vary within an inheritance tree.  In the targetlists, Vars
-	 * referencing the result relation will have their original varno and
-	 * varattno, while Vars referencing other rels will be converted to have
-	 * varno OUTER and varattno referencing a resjunk entry in the top plan
-	 * node's targetlist.  XXX This field ought to go in some sort of TopPlan
-	 * plan node, not in the Query.
-	 */
-	List	   *returningLists; /* list of lists of TargetEntry, or NIL */
 } Query;
 
 
@@ -761,17 +729,10 @@ typedef struct SelectStmt
 
 	/*
 	 * These fields are used only in "leaf" SelectStmts.
-	 *
-	 * into, intoColNames, intoOptions, intoOnCommit, and intoTableSpaceName
-	 * are a kluge; they belong somewhere else...
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
-	RangeVar   *into;			/* target table (for select into table) */
-	List	   *intoColNames;	/* column names for into table */
-	List	   *intoOptions;	/* options from WITH clause */
-	OnCommitAction intoOnCommit;	/* what do we do at COMMIT? */
-	char	   *intoTableSpaceName;		/* table space to use, or NULL */
+	IntoClause *into;			/* target for SELECT INTO / CREATE TABLE AS */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
 	Node	   *whereClause;	/* WHERE qualification */
@@ -1994,10 +1955,7 @@ typedef struct ExecuteStmt
 {
 	NodeTag		type;
 	char	   *name;			/* The name of the plan to execute */
-	RangeVar   *into;			/* Optional table to store results in */
-	List	   *intoOptions;	/* Options from WITH clause */
-	OnCommitAction into_on_commit;		/* What do we do at COMMIT? */
-	char	   *into_tbl_space; /* Tablespace to use, or NULL */
+	IntoClause *into;			/* Optional table to store results in */
 	List	   *params;			/* Values to assign to parameters */
 } ExecuteStmt;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cdd7b4d2e43..537981462b2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.90 2007/02/19 02:23:12 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.91 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,48 @@
  * ----------------------------------------------------------------
  */
 
+/* ----------------
+ *		PlannedStmt node
+ *
+ * The output of the planner is a Plan tree headed by a PlannedStmt node.
+ * PlannedStmt holds the "one time" information needed by the executor.
+ * ----------------
+ */
+typedef struct PlannedStmt
+{
+	NodeTag		type;
+
+	CmdType		commandType;	/* select|insert|update|delete */
+
+	bool		canSetTag;		/* do I set the command result tag? */
+
+	struct Plan *planTree;		/* tree of Plan nodes */
+
+	List	   *rtable;			/* list of RangeTblEntry nodes */
+
+	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
+	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
+
+	IntoClause *into;			/* target for SELECT INTO / CREATE TABLE AS */
+
+	/*
+	 * If the query has a returningList then the planner will store a list of
+	 * processed targetlists (one per result relation) here.  We must have a
+	 * separate RETURNING targetlist for each result rel because column
+	 * numbers may vary within an inheritance tree.  In the targetlists, Vars
+	 * referencing the result relation will have their original varno and
+	 * varattno, while Vars referencing other rels will be converted to have
+	 * varno OUTER and varattno referencing a resjunk entry in the top plan
+	 * node's targetlist.
+	 */
+	List	   *returningLists; /* list of lists of TargetEntry, or NIL */
+
+	List	   *rowMarks;		/* a list of RowMarkClause's */
+
+	int			nParamExec;		/* number of PARAM_EXEC Params used */
+} PlannedStmt;
+
+
 /* ----------------
  *		Plan node
  *
@@ -75,15 +117,6 @@ typedef struct Plan
 	 */
 	Bitmapset  *extParam;
 	Bitmapset  *allParam;
-
-	/*
-	 * We really need in some TopPlan node to store range table and
-	 * resultRelation from Query there and get rid of Query itself from
-	 * Executor. Some other stuff like below could be put there, too.
-	 */
-	int			nParamExec;		/* Number of them in entire query. This is to
-								 * get Executor know about how many PARAM_EXEC
-								 * there are in query plan. */
 } Plan;
 
 /* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index caa689e2623..185673f729a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.125 2007/02/19 07:03:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.126 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,6 +49,15 @@ typedef enum InhOption
 	INH_DEFAULT					/* Use current SQL_inheritance option */
 } InhOption;
 
+/* What to do at commit time for temporary relations */
+typedef enum OnCommitAction
+{
+	ONCOMMIT_NOOP,				/* No ON COMMIT clause (do nothing) */
+	ONCOMMIT_PRESERVE_ROWS,		/* ON COMMIT PRESERVE ROWS (do nothing) */
+	ONCOMMIT_DELETE_ROWS,		/* ON COMMIT DELETE ROWS */
+	ONCOMMIT_DROP				/* ON COMMIT DROP */
+} OnCommitAction;
+
 /*
  * RangeVar - range variable, used in FROM clauses
  *
@@ -69,6 +78,20 @@ typedef struct RangeVar
 	Alias	   *alias;			/* table alias & optional column aliases */
 } RangeVar;
 
+/*
+ * IntoClause - target information for SELECT INTO and CREATE TABLE AS
+ */
+typedef struct IntoClause
+{
+	NodeTag		type;
+
+	RangeVar   *rel;			/* target relation name */
+	List	   *colNames;		/* column names to assign, or NIL */
+	List	   *options;		/* options from WITH clause */
+	OnCommitAction onCommit;	/* what do we do at COMMIT? */
+	char	   *tableSpaceName;	/* table space to use, or NULL */
+} IntoClause;
+
 
 /* ----------------------------------------------------------------
  *					node types for executable expressions
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 6de06ebc918..59ec830f3ff 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.136 2007/02/19 07:03:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.137 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -110,6 +110,10 @@ typedef struct PlannerInfo
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
 	struct HTAB *join_rel_hash; /* optional hashtable for join relations */
 
+	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
+
+	List	   *returningLists;		/* list of lists of TargetEntry, or NIL */
+
 	List	   *init_plans;				/* init subplans for query */
 
 	List	   *eq_classes;				/* list of active EquivalenceClasses */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 44d03602694..c243cdbc356 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.37 2007/02/19 07:03:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.38 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,10 +18,10 @@
 #include "nodes/relation.h"
 
 
-extern Plan *planner(Query *parse, bool isCursor, int cursorOptions,
+extern PlannedStmt *planner(Query *parse, bool isCursor, int cursorOptions,
 		ParamListInfo boundParams);
 extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 							  Index level, double tuple_fraction,
-							  List **subquery_pathkeys);
+							  PlannerInfo **subroot);
 
 #endif   /* PLANNER_H */
diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h
index 35313530271..5cab498c13a 100644
--- a/src/include/tcop/pquery.h
+++ b/src/include/tcop/pquery.h
@@ -7,23 +7,26 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.40 2007/01/05 22:19:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.41 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PQUERY_H
 #define PQUERY_H
 
+#include "nodes/parsenodes.h"
 #include "utils/portal.h"
 
 
 extern DLLIMPORT Portal ActivePortal;
 
 
-extern PortalStrategy ChoosePortalStrategy(List *parseTrees);
+extern PortalStrategy ChoosePortalStrategy(List *stmts);
 
 extern List *FetchPortalTargetList(Portal portal);
 
+extern List *FetchStatementTargetList(Node *stmt);
+
 extern void PortalStart(Portal portal, ParamListInfo params,
 			Snapshot snapshot);
 
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index ad66a61dace..2a551255766 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.86 2007/01/05 22:19:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.87 2007/02/20 17:32:17 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -20,6 +20,7 @@
 #define TCOPPROT_H
 
 #include "executor/execdesc.h"
+#include "nodes/parsenodes.h"
 #include "utils/guc.h"
 
 
@@ -50,7 +51,7 @@ extern List *pg_parse_and_rewrite(const char *query_string,
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
 					   Oid *paramTypes, int numParams);
-extern Plan *pg_plan_query(Query *querytree, ParamListInfo boundParams);
+extern PlannedStmt *pg_plan_query(Query *querytree, ParamListInfo boundParams);
 extern List *pg_plan_queries(List *querytrees, ParamListInfo boundParams,
 				bool needSnapshot);
 
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index ebcaf597729..52c02253068 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.30 2007/01/05 22:19:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.31 2007/02/20 17:32:17 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,15 +26,9 @@ extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
 
 extern const char *CreateCommandTag(Node *parsetree);
 
-extern const char *CreateQueryTag(Query *parsetree);
-
 extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
 
-extern LogStmtLevel GetQueryLogLevel(Query *parsetree);
-
-extern bool QueryReturnsTuples(Query *parsetree);
-
-extern bool QueryIsReadOnly(Query *parsetree);
+extern bool CommandIsReadOnly(Node *parsetree);
 
 extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
 
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 5eb2c715e30..aa432abb876 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -12,7 +12,7 @@
  * to let the client suspend an update-type query partway through!	Because
  * the query rewriter does not allow arbitrary ON SELECT rewrite rules,
  * only queries that were originally update-type could produce multiple
- * parse/plan trees; so the restriction to a single query is not a problem
+ * plan trees; so the restriction to a single query is not a problem
  * in practice.
  *
  * For SQL cursors, we support three kinds of scroll behavior:
@@ -39,7 +39,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.72 2007/01/05 22:19:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.73 2007/02/20 17:32:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -124,9 +124,8 @@ typedef struct PortalData
 	/* The query or queries the portal will execute */
 	const char *sourceText;		/* text of query, if known (may be NULL) */
 	const char *commandTag;		/* command tag for original query */
-	List	   *parseTrees;		/* parse tree(s) */
-	List	   *planTrees;		/* plan tree(s) */
-	MemoryContext queryContext; /* where the parse trees live */
+	List	   *stmts;			/* PlannedStmts and/or utility statements */
+	MemoryContext queryContext; /* where the plan trees live */
 
 	/*
 	 * Note: queryContext effectively identifies which prepared statement the
@@ -191,7 +190,7 @@ typedef struct PortalData
  */
 #define PortalGetQueryDesc(portal)	((portal)->queryDesc)
 #define PortalGetHeapMemory(portal) ((portal)->heap)
-#define PortalGetPrimaryQuery(portal) PortalListGetPrimaryQuery((portal)->parseTrees)
+#define PortalGetPrimaryStmt(portal) PortalListGetPrimaryStmt((portal)->stmts)
 
 
 /* Prototypes for functions in utils/mmgr/portalmem.c */
@@ -217,10 +216,9 @@ extern void PortalDefineQuery(Portal portal,
 				  const char *prepStmtName,
 				  const char *sourceText,
 				  const char *commandTag,
-				  List *parseTrees,
-				  List *planTrees,
+				  List *stmts,
 				  MemoryContext queryContext);
-extern Query *PortalListGetPrimaryQuery(List *parseTrees);
+extern Node *PortalListGetPrimaryStmt(List *stmts);
 extern void PortalCreateHoldStore(Portal portal);
 
 #endif   /* PORTAL_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 09f7a99097d..68a68a6634c 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.188 2007/02/08 18:37:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.189 2007/02/20 17:32:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2380,20 +2380,20 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 		exec_prepare_plan(estate, expr);
 		stmt->mod_stmt = false;
 		spi_plan = (_SPI_plan *) expr->plan;
-		foreach(l, spi_plan->qtlist)
+		foreach(l, spi_plan->stmt_list_list)
 		{
 			ListCell   *l2;
 
 			foreach(l2, (List *) lfirst(l))
 			{
-				Query	   *q = (Query *) lfirst(l2);
+				PlannedStmt *p = (PlannedStmt *) lfirst(l2);
 
-				Assert(IsA(q, Query));
-				if (q->canSetTag)
+				if (IsA(p, PlannedStmt) &&
+					p->canSetTag)
 				{
-					if (q->commandType == CMD_INSERT ||
-						q->commandType == CMD_UPDATE ||
-						q->commandType == CMD_DELETE)
+					if (p->commandType == CMD_INSERT ||
+						p->commandType == CMD_UPDATE ||
+						p->commandType == CMD_DELETE)
 						stmt->mod_stmt = true;
 				}
 			}
@@ -4674,6 +4674,8 @@ static void
 exec_simple_check_plan(PLpgSQL_expr *expr)
 {
 	_SPI_plan  *spi_plan = (_SPI_plan *) expr->plan;
+	List	   *sublist;
+	PlannedStmt *stmt;
 	Plan	   *plan;
 	TargetEntry *tle;
 
@@ -4683,17 +4685,20 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
 	 * 1. We can only evaluate queries that resulted in one single execution
 	 * plan
 	 */
-	if (list_length(spi_plan->ptlist) != 1)
+	if (list_length(spi_plan->stmt_list_list) != 1)
+		return;
+	sublist = (List *) linitial(spi_plan->stmt_list_list);
+	if (list_length(sublist) != 1)
 		return;
 
-	plan = (Plan *) linitial(spi_plan->ptlist);
+	stmt = (PlannedStmt *) linitial(sublist);
 
 	/*
 	 * 2. It must be a RESULT plan --> no scan's required
 	 */
-	if (plan == NULL)			/* utility statement produces this */
+	if (!IsA(stmt, PlannedStmt))
 		return;
-
+	plan = stmt->planTree;
 	if (!IsA(plan, Result))
 		return;
 
-- 
GitLab