diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be33bb76c4ff53432408bd2f7596a775c5c765b9..885411cf2c0286532c0f11c9fd401dec3b0eedde 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.282 2007/04/18 02:28:22 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.283 2007/04/27 22:05:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1015,9 +1015,10 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 
 		query = (Query *) linitial(rewritten);
 		Assert(query->commandType == CMD_SELECT);
+		Assert(query->utilityStmt == NULL);
 
 		/* Query mustn't use INTO, either */
-		if (query->into)
+		if (query->intoClause)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("COPY (SELECT INTO) is not supported")));
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index bb7d43035989e05684af9d8811dbf55736d5eec2..592eeba4177bb81e5a06e46f4c3d211bbc5e0b8f 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.161 2007/04/16 01:14:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.162 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,8 +42,8 @@ typedef struct ExplainState
 	List	   *rtable;			/* range table */
 } ExplainState;
 
-static void ExplainOneQuery(Query *query, int cursorOptions,
-							ExplainStmt *stmt, const char *queryString,
+static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
+							const char *queryString,
 							ParamListInfo params, TupOutputState *tstate);
 static double elapsed_time(instr_time *starttime);
 static void explain_outNode(StringInfo str,
@@ -102,8 +102,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 		/* Explain every plan */
 		foreach(l, rewritten)
 		{
-			ExplainOneQuery((Query *) lfirst(l), 0,
-							stmt, queryString, params, tstate);
+			ExplainOneQuery((Query *) lfirst(l), stmt,
+							queryString, params, tstate);
 			/* put a blank line between plans */
 			if (lnext(l) != NULL)
 				do_text_output_oneline(tstate, "");
@@ -134,8 +134,7 @@ ExplainResultDesc(ExplainStmt *stmt)
  *	  print out the execution plan for one Query
  */
 static void
-ExplainOneQuery(Query *query, int cursorOptions,
-				ExplainStmt *stmt, const char *queryString,
+ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString,
 				ParamListInfo params, TupOutputState *tstate)
 {
 	PlannedStmt *plan;
@@ -150,7 +149,7 @@ ExplainOneQuery(Query *query, int cursorOptions,
 	}
 
 	/* plan the query */
-	plan = planner(query, cursorOptions, params);
+	plan = planner(query, 0, params);
 
 	/*
 	 * Update snapshot command ID to ensure this query sees results of any
@@ -187,52 +186,7 @@ ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
 	if (utilityStmt == NULL)
 		return;
 
-	if (IsA(utilityStmt, DeclareCursorStmt))
-	{
-		DeclareCursorStmt *dcstmt = (DeclareCursorStmt *) utilityStmt;
-		Oid		   *param_types;
-		int			num_params;
-		Query	   *query;
-		List	   *rewritten;
-		ExplainStmt newstmt;
-
-		/* Convert parameter type data to the form parser wants */
-		getParamListTypes(params, &param_types, &num_params);
-
-		/*
-		 * Run parse analysis and rewrite.  Note this also acquires sufficient
-		 * locks on the source table(s).
-		 *
-		 * Because the parser and planner tend to scribble on their input, we
-		 * make a preliminary copy of the source querytree.  This prevents
-		 * problems in the case that the DECLARE CURSOR is in a portal or
-		 * plpgsql function and is executed repeatedly.  (See also the same
-		 * hack in COPY and PREPARE.)  XXX FIXME someday.
-		 */
-		rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query),
-										   queryString,
-										   param_types, num_params);
-
-		/* We don't expect more or less than one result query */
-		if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
-			elog(ERROR, "unexpected rewrite result");
-		query = (Query *) linitial(rewritten);
-		if (query->commandType != CMD_SELECT)
-			elog(ERROR, "unexpected rewrite result");
-
-		/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
-		if (query->into)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-					 errmsg("DECLARE CURSOR cannot specify INTO")));
-
-		/* do not actually execute the underlying query! */
-		memcpy(&newstmt, stmt, sizeof(ExplainStmt));
-		newstmt.analyze = false;
-		ExplainOneQuery(query, dcstmt->options, &newstmt,
-						queryString, params, tstate);
-	}
-	else if (IsA(utilityStmt, ExecuteStmt))
+	if (IsA(utilityStmt, ExecuteStmt))
 		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
 							queryString, params, tstate);
 	else if (IsA(utilityStmt, NotifyStmt))
@@ -247,6 +201,11 @@ ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
  *		given a planned query, execute it if needed, and then print
  *		EXPLAIN output
  *
+ * Since we ignore any DeclareCursorStmt that might be attached to the query,
+ * if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
+ * query.  This is different from pre-8.3 behavior but seems more useful than
+ * not running the query.  No cursor will be created, however.
+ *
  * This is exported because it's called back from prepare.c in the
  * EXPLAIN EXECUTE case
  *
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index eb381ebb7052ce609a4e09a759294b1e4ba75395..939452650d088428b78c3caa2a4a3462d7719a87 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.64 2007/04/16 01:14:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.65 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,34 +26,34 @@
 #include "access/xact.h"
 #include "commands/portalcmds.h"
 #include "executor/executor.h"
-#include "optimizer/planner.h"
-#include "rewrite/rewriteHandler.h"
 #include "tcop/pquery.h"
-#include "tcop/tcopprot.h"
 #include "utils/memutils.h"
 
 
 /*
  * PerformCursorOpen
  *		Execute SQL DECLARE CURSOR command.
+ *
+ * The query has already been through parse analysis, rewriting, and planning.
+ * When it gets here, it looks like a SELECT PlannedStmt, except that the
+ * utilityStmt field is set.
  */
 void
-PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
+PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
 				  const char *queryString, bool isTopLevel)
 {
-	Oid		   *param_types;
-	int			num_params;
-	List	   *rewritten;
-	Query	   *query;
-	PlannedStmt *plan;
+	DeclareCursorStmt *cstmt = (DeclareCursorStmt *) stmt->utilityStmt;
 	Portal		portal;
 	MemoryContext oldContext;
 
+	if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
+		elog(ERROR, "PerformCursorOpen called for non-cursor query");
+
 	/*
 	 * Disallow empty-string cursor name (conflicts with protocol-level
 	 * unnamed portal).
 	 */
-	if (!stmt->portalname || stmt->portalname[0] == '\0')
+	if (!cstmt->portalname || cstmt->portalname[0] == '\0')
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_CURSOR_NAME),
 				 errmsg("invalid cursor name: must not be empty")));
@@ -63,70 +63,24 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
 	 * been executed inside a transaction block (or else, it would have no
 	 * user-visible effect).
 	 */
-	if (!(stmt->options & CURSOR_OPT_HOLD))
+	if (!(cstmt->options & CURSOR_OPT_HOLD))
 		RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
 
-	/*
-	 * Don't allow both SCROLL and NO SCROLL to be specified
-	 */
-	if ((stmt->options & CURSOR_OPT_SCROLL) &&
-		(stmt->options & CURSOR_OPT_NO_SCROLL))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-				 errmsg("cannot specify both SCROLL and NO SCROLL")));
-
-	/* Convert parameter type data to the form parser wants */
-	getParamListTypes(params, &param_types, &num_params);
-
-	/*
-	 * Run parse analysis and rewrite.  Note this also acquires sufficient
-	 * locks on the source table(s).
-	 *
-	 * Because the parser and planner tend to scribble on their input, we
-	 * make a preliminary copy of the source querytree.  This prevents
-	 * problems in the case that the DECLARE CURSOR is in a portal or plpgsql
-	 * function and is executed repeatedly.  (See also the same hack in
-	 * COPY and PREPARE.)  XXX FIXME someday.
-	 */
-	rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
-									   queryString, param_types, num_params);
-
-	/* We don't expect more or less than one result query */
-	if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
-		elog(ERROR, "unexpected rewrite result");
-	query = (Query *) linitial(rewritten);
-	if (query->commandType != CMD_SELECT)
-		elog(ERROR, "unexpected rewrite result");
-
-	/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
-	if (query->into)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-				 errmsg("DECLARE CURSOR cannot specify INTO")));
-
-	if (query->rowMarks != NIL)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			  errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
-				 errdetail("Cursors must be READ ONLY.")));
-
-	/* plan the query */
-	plan = planner(query, stmt->options, params);
-
 	/*
 	 * Create a portal and copy the plan into its memory context.
 	 */
-	portal = CreatePortal(stmt->portalname, false, false);
+	portal = CreatePortal(cstmt->portalname, false, false);
 
 	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
-	plan = copyObject(plan);
+	stmt = copyObject(stmt);
+	stmt->utilityStmt = NULL;	/* make it look like plain SELECT */
 
 	PortalDefineQuery(portal,
 					  NULL,
 					  queryString,
 					  "SELECT", /* cursor's query is always a SELECT */
-					  list_make1(plan),
+					  list_make1(stmt),
 					  NULL);
 
 	/*----------
@@ -150,10 +104,10 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
 	 * based on whether it would require any additional runtime overhead to do
 	 * so.
 	 */
-	portal->cursorOptions = stmt->options;
+	portal->cursorOptions = cstmt->options;
 	if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
 	{
-		if (ExecSupportsBackwardScan(plan->planTree))
+		if (ExecSupportsBackwardScan(stmt->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 e9b953f709a107050f722ed5c78960a2b065f8d2..997f66c81884629caa8602619c31afa72a3d5013 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.74 2007/04/26 23:24:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.75 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -57,7 +57,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 	int			nargs;
 	List	   *queries;
 	Query	   *query;
-	const char *commandTag;
 	List	   *query_list,
 			   *plan_list;
 	int			i;
@@ -137,22 +136,15 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 	switch (query->commandType)
 	{
 		case CMD_SELECT:
-			commandTag = "SELECT";
-			break;
 		case CMD_INSERT:
-			commandTag = "INSERT";
-			break;
 		case CMD_UPDATE:
-			commandTag = "UPDATE";
-			break;
 		case CMD_DELETE:
-			commandTag = "DELETE";
+			/* OK */
 			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
 					 errmsg("utility statements cannot be prepared")));
-			commandTag = NULL;	/* keep compiler quiet */
 			break;
 	}
 
@@ -168,7 +160,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 	StorePreparedStatement(stmt->name,
 						   stmt->query,
 						   queryString,
-						   commandTag,
+						   CreateCommandTag((Node *) query),
 						   argtypes,
 						   nargs,
 						   0,				/* default cursor options */
@@ -244,11 +236,12 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
 					 errmsg("prepared statement is not a SELECT")));
 		pstmt = (PlannedStmt *) linitial(plan_list);
 		if (!IsA(pstmt, PlannedStmt) ||
-			pstmt->commandType != CMD_SELECT)
+			pstmt->commandType != CMD_SELECT ||
+			pstmt->utilityStmt != NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("prepared statement is not a SELECT")));
-		pstmt->into = copyObject(stmt->into);
+		pstmt->intoClause = copyObject(stmt->into);
 
 		MemoryContextSwitchTo(oldContext);
 
@@ -689,7 +682,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
 
 			if (execstmt->into)
 			{
-				if (pstmt->commandType != CMD_SELECT)
+				if (pstmt->commandType != CMD_SELECT ||
+					pstmt->utilityStmt != NULL)
 					ereport(ERROR,
 							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 							 errmsg("prepared statement is not a SELECT")));
@@ -697,7 +691,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
 				/* Copy the stmt so we can modify it */
 				pstmt = copyObject(pstmt);
 
-				pstmt->into = execstmt->into;
+				pstmt->intoClause = execstmt->into;
 			}
 
 			/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index cacd7c6fe7099235367580dce4e0ff0705a118a2..298d0c69c9deea70d7117dde9349ac7f4064bdcd 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.292 2007/03/29 00:15:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.293 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -420,7 +420,7 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 	 *
 	 * XXX should we allow this if the destination is temp?
 	 */
-	if (plannedstmt->into != NULL)
+	if (plannedstmt->intoClause != NULL)
 		goto fail;
 
 	/* Fail if write permissions are requested on any non-temp table */
@@ -522,10 +522,10 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	 * correct tuple descriptors.  (Other SELECT INTO stuff comes later.)
 	 */
 	estate->es_select_into = false;
-	if (operation == CMD_SELECT && plannedstmt->into != NULL)
+	if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
 	{
 		estate->es_select_into = true;
-		estate->es_into_oids = interpretOidsOption(plannedstmt->into->options);
+		estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
 	}
 
 	/*
@@ -2395,7 +2395,7 @@ typedef struct
 static void
 OpenIntoRel(QueryDesc *queryDesc)
 {
-	IntoClause *into = queryDesc->plannedstmt->into;
+	IntoClause *into = queryDesc->plannedstmt->intoClause;
 	EState	   *estate = queryDesc->estate;
 	Relation	intoRelationDesc;
 	char	   *intoName;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index b59228b0c9ee199112e1a999cded253e936b94da..9ad1391612362d2015c1713944cd0a782a489c0a 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.115 2007/04/16 01:14:56 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.116 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -317,7 +317,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 	/* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
 
 	/* Utility commands don't need Executor. */
-	if (es->qd->operation != CMD_UTILITY)
+	if (es->qd->utilitystmt == NULL)
 	{
 		/*
 		 * Only set up to collect queued triggers if it's not a SELECT.
@@ -346,9 +346,12 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
 	{
 		ActiveSnapshot = es->qd->snapshot;
 
-		if (es->qd->operation == CMD_UTILITY)
+		if (es->qd->utilitystmt)
 		{
-			ProcessUtility(es->qd->utilitystmt,
+			/* ProcessUtility needs the PlannedStmt for DECLARE CURSOR */
+			ProcessUtility((es->qd->plannedstmt ?
+							(Node *) es->qd->plannedstmt :
+							es->qd->utilitystmt),
 						   fcache->src,
 						   es->qd->params,
 						   false,				/* not top level */
@@ -366,7 +369,8 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
 			 */
 			if (LAST_POSTQUEL_COMMAND(es) &&
 				es->qd->operation == CMD_SELECT &&
-				es->qd->plannedstmt->into == NULL)
+				es->qd->plannedstmt->utilityStmt == NULL &&
+				es->qd->plannedstmt->intoClause == NULL)
 				count = 1L;
 			else
 				count = 0L;
@@ -396,7 +400,7 @@ postquel_end(execution_state *es)
 	es->status = F_EXEC_DONE;
 
 	/* Utility commands don't need Executor. */
-	if (es->qd->operation != CMD_UTILITY)
+	if (es->qd->utilitystmt == NULL)
 	{
 		/* Make our snapshot the active one for any called functions */
 		saveActiveSnapshot = ActiveSnapshot;
@@ -894,7 +898,9 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 	 * Note: eventually replace this test with QueryReturnsTuples?  We'd need
 	 * a more general method of determining the output type, though.
 	 */
-	if (!(parse->commandType == CMD_SELECT && parse->into == NULL))
+	if (!(parse->commandType == CMD_SELECT &&
+		  parse->utilityStmt == NULL &&
+		  parse->intoClause == NULL))
 	{
 		if (rettype != VOIDOID)
 			ereport(ERROR,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 0a5625a0c583b60fdfad1ba4ef9edeb11be5c7e1..f97a86fe214662b2508ad0fe5bd8f93be29a9500 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.178 2007/04/16 18:21:07 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.179 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1581,7 +1581,8 @@ _SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
 						ActiveSnapshot->curcid = GetCurrentCommandId();
 				}
 
-				if (IsA(stmt, PlannedStmt))
+				if (IsA(stmt, PlannedStmt) &&
+					((PlannedStmt *) stmt)->utilityStmt == NULL)
 				{
 					qdesc = CreateQueryDesc((PlannedStmt *) stmt,
 											ActiveSnapshot,
@@ -1687,7 +1688,8 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount)
 	switch (operation)
 	{
 		case CMD_SELECT:
-			if (queryDesc->plannedstmt->into)		/* select into table? */
+			Assert(queryDesc->plannedstmt->utilityStmt == NULL);
+			if (queryDesc->plannedstmt->intoClause)	/* select into table? */
 				res = SPI_OK_SELINTO;
 			else if (queryDesc->dest->mydest != DestSPI)
 			{
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 988120a963496c89998f3f1b7ba7d1161648d08b..62853461a897b1c3bea175df5d398d46654bbd5b 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.374 2007/04/26 16:13:10 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.375 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,7 +77,8 @@ _copyPlannedStmt(PlannedStmt *from)
 	COPY_NODE_FIELD(planTree);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(resultRelations);
-	COPY_NODE_FIELD(into);
+	COPY_NODE_FIELD(utilityStmt);
+	COPY_NODE_FIELD(intoClause);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(returningLists);
@@ -1819,7 +1820,7 @@ _copyQuery(Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
-	COPY_NODE_FIELD(into);
+	COPY_NODE_FIELD(intoClause);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasSubLinks);
 	COPY_NODE_FIELD(rtable);
@@ -1884,7 +1885,7 @@ _copySelectStmt(SelectStmt *from)
 	SelectStmt *newnode = makeNode(SelectStmt);
 
 	COPY_NODE_FIELD(distinctClause);
-	COPY_NODE_FIELD(into);
+	COPY_NODE_FIELD(intoClause);
 	COPY_NODE_FIELD(targetList);
 	COPY_NODE_FIELD(fromClause);
 	COPY_NODE_FIELD(whereClause);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c1500948dcaf44dd1f3da77a316e8dab355bc640..99fb8f18b6f59358c08916023fd6074edd863d37 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.305 2007/04/26 16:13:11 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.306 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -722,7 +722,7 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
-	COMPARE_NODE_FIELD(into);
+	COMPARE_NODE_FIELD(intoClause);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
 	COMPARE_NODE_FIELD(rtable);
@@ -779,7 +779,7 @@ static bool
 _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 {
 	COMPARE_NODE_FIELD(distinctClause);
-	COMPARE_NODE_FIELD(into);
+	COMPARE_NODE_FIELD(intoClause);
 	COMPARE_NODE_FIELD(targetList);
 	COMPARE_NODE_FIELD(fromClause);
 	COMPARE_NODE_FIELD(whereClause);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 49202f8664a3df2805ab403a9e44caa7cbbfa89d..58d8ee620bcfe1275bc24d833842a07badf3e3ef 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.305 2007/03/27 23:21:09 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.306 2007/04/27 22:05:47 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -244,7 +244,8 @@ _outPlannedStmt(StringInfo str, PlannedStmt *node)
 	WRITE_NODE_FIELD(planTree);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(resultRelations);
-	WRITE_NODE_FIELD(into);
+	WRITE_NODE_FIELD(utilityStmt);
+	WRITE_NODE_FIELD(intoClause);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(returningLists);
@@ -1549,7 +1550,7 @@ _outSelectStmt(StringInfo str, SelectStmt *node)
 	WRITE_NODE_TYPE("SELECT");
 
 	WRITE_NODE_FIELD(distinctClause);
-	WRITE_NODE_FIELD(into);
+	WRITE_NODE_FIELD(intoClause);
 	WRITE_NODE_FIELD(targetList);
 	WRITE_NODE_FIELD(fromClause);
 	WRITE_NODE_FIELD(whereClause);
@@ -1694,7 +1695,7 @@ _outQuery(StringInfo str, Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
-	WRITE_NODE_FIELD(into);
+	WRITE_NODE_FIELD(intoClause);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasSubLinks);
 	WRITE_NODE_FIELD(rtable);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1f3b81e275f335b1117bee7d4be8c3b975db574f..be450e94c02f275fe635998103e97ac2629e50f3 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.205 2007/03/27 23:21:09 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.206 2007/04/27 22:05:47 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -139,7 +139,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
-	READ_NODE_FIELD(into);
+	READ_NODE_FIELD(intoClause);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasSubLinks);
 	READ_NODE_FIELD(rtable);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 92ade2fccf56a08831d0be2bc50e3464c98a174d..16d71af183d0108b35f130ae4e483987c0340778 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.31 2007/04/21 21:01:45 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.32 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -454,7 +454,8 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
 	subparse->commandType = CMD_SELECT;
 	subparse->resultRelation = 0;
 	subparse->returningList = NIL;
-	subparse->into = NULL;
+	subparse->utilityStmt = NULL;
+	subparse->intoClause = NULL;
 	subparse->hasAggs = false;
 	subparse->groupClause = NIL;
 	subparse->havingQual = NULL;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5c65a78d6f727be5e75b1271ae2944aa6ee9359a..d5dc6f7c7e6fb31b824e59a1f3c7153fbfc6a51e 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.217 2007/04/16 01:14:56 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.218 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,6 +90,11 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	ListCell   *lp,
 			   *lr;
 
+	/* Cursor options may come from caller or from DECLARE CURSOR stmt */
+	if (parse->utilityStmt &&
+		IsA(parse->utilityStmt, DeclareCursorStmt))
+		cursorOptions |= ((DeclareCursorStmt *) parse->utilityStmt)->options;
+
 	/*
 	 * Set up global state for this planner invocation.  This data is needed
 	 * across all levels of sub-Query that might exist in the given command,
@@ -156,7 +161,8 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->planTree = top_plan;
 	result->rtable = glob->finalrtable;
 	result->resultRelations = root->resultRelations;
-	result->into = parse->into;
+	result->utilityStmt = parse->utilityStmt;
+	result->intoClause = parse->intoClause;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->returningLists = root->returningLists;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1519c01e60e725c8c5ad72ddd0a6f96fda58edc5..9ba293839cc4eb40462ba8d0ce0e393e905e2b06 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.47 2007/02/19 07:03:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.48 2007/04/27 22:05:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -627,7 +627,8 @@ is_simple_subquery(Query *subquery)
 	 */
 	if (!IsA(subquery, Query) ||
 		subquery->commandType != CMD_SELECT ||
-		subquery->into != NULL)
+		subquery->utilityStmt != NULL ||
+		subquery->intoClause != NULL)
 		elog(ERROR, "subquery is bogus");
 
 	/*
@@ -698,7 +699,8 @@ is_simple_union_all(Query *subquery)
 	/* Let's just make sure it's a valid subselect ... */
 	if (!IsA(subquery, Query) ||
 		subquery->commandType != CMD_SELECT ||
-		subquery->into != NULL)
+		subquery->utilityStmt != NULL ||
+		subquery->intoClause != NULL)
 		elog(ERROR, "subquery is bogus");
 
 	/* Is it a set-operation query at all? */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d4cb3f06b05a71963ab4953142c9c7a76e1d376c..8b646516de855a93eab8842a0c93d44fa1ac0238 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.241 2007/04/02 03:49:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.242 2007/04/27 22:05:48 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -2945,7 +2945,8 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	 */
 	if (!IsA(querytree, Query) ||
 		querytree->commandType != CMD_SELECT ||
-		querytree->into ||
+		querytree->utilityStmt ||
+		querytree->intoClause ||
 		querytree->hasAggs ||
 		querytree->hasSubLinks ||
 		querytree->rtable ||
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a4e4418b145034cc460eb1f09f31061fd7ccc2bb..68475387be367779f91683b0588779bcebd0d9f0 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -20,7 +20,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.362 2007/03/13 00:33:41 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.363 2007/04/27 22:05:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -118,6 +118,10 @@ static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
 static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
+static Query *transformDeclareCursorStmt(ParseState *pstate,
+						   DeclareCursorStmt *stmt);
+static Query *transformExplainStmt(ParseState *pstate,
+						   ExplainStmt *stmt);
 static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
 					List **extras_before, List **extras_after);
 static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
@@ -312,20 +316,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
 
 	switch (nodeTag(parseTree))
 	{
-			/*
-			 * Non-optimizable statements
-			 */
-		case T_CreateStmt:
-			result = transformCreateStmt(pstate, (CreateStmt *) parseTree,
-										 extras_before, extras_after);
-			break;
-
-		case T_AlterTableStmt:
-			result = transformAlterTableStmt(pstate,
-											 (AlterTableStmt *) parseTree,
-											 extras_before, extras_after);
-			break;
-
 			/*
 			 * Optimizable statements
 			 */
@@ -355,6 +345,33 @@ transformStmt(ParseState *pstate, Node *parseTree,
 			}
 			break;
 
+			/*
+			 * Non-optimizable statements
+			 */
+		case T_CreateStmt:
+			result = transformCreateStmt(pstate, (CreateStmt *) parseTree,
+										 extras_before, extras_after);
+			break;
+
+		case T_AlterTableStmt:
+			result = transformAlterTableStmt(pstate,
+											 (AlterTableStmt *) parseTree,
+											 extras_before, extras_after);
+			break;
+
+			/*
+			 * Special cases
+			 */
+		case T_DeclareCursorStmt:
+			result = transformDeclareCursorStmt(pstate,
+											(DeclareCursorStmt *) parseTree);
+			break;
+
+		case T_ExplainStmt:
+			result = transformExplainStmt(pstate,
+										  (ExplainStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -546,9 +563,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
 		release_pstate_resources(sub_pstate);
 		pfree(sub_pstate);
 
+		/* The grammar should have produced a SELECT, but it might have INTO */
 		Assert(IsA(selectQuery, Query));
 		Assert(selectQuery->commandType == CMD_SELECT);
-		if (selectQuery->into)
+		Assert(selectQuery->utilityStmt == NULL);
+		if (selectQuery->intoClause)
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("INSERT ... SELECT cannot specify INTO")));
@@ -2029,6 +2048,8 @@ analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
 /*
  * transformSelectStmt -
  *	  transforms a Select Statement
+ *
+ * Note: this is also used for DECLARE CURSOR statements.
  */
 static Query *
 transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
@@ -2085,11 +2106,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 										   "LIMIT");
 
 	/* handle any SELECT INTO/CREATE TABLE AS spec */
-	if (stmt->into)
+	if (stmt->intoClause)
 	{
-		qry->into = stmt->into;
-		if (stmt->into->colNames)
-			applyColumnNames(qry->targetList, stmt->into->colNames);
+		qry->intoClause = stmt->intoClause;
+		if (stmt->intoClause->colNames)
+			applyColumnNames(qry->targetList, stmt->intoClause->colNames);
 	}
 
 	qry->rtable = pstate->p_rtable;
@@ -2254,11 +2275,11 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 			 errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
 
 	/* handle any CREATE TABLE AS spec */
-	if (stmt->into)
+	if (stmt->intoClause)
 	{
-		qry->into = stmt->into;
-		if (stmt->into->colNames)
-			applyColumnNames(qry->targetList, stmt->into->colNames);
+		qry->intoClause = stmt->intoClause;
+		if (stmt->intoClause->colNames)
+			applyColumnNames(qry->targetList, stmt->intoClause->colNames);
 	}
 
 	/*
@@ -2345,14 +2366,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 		leftmostSelect = leftmostSelect->larg;
 	Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
 		   leftmostSelect->larg == NULL);
-	if (leftmostSelect->into)
+	if (leftmostSelect->intoClause)
 	{
-		qry->into = leftmostSelect->into;
-		intoColNames = leftmostSelect->into->colNames;
+		qry->intoClause = leftmostSelect->intoClause;
+		intoColNames = leftmostSelect->intoClause->colNames;
 	}
 
 	/* clear this to prevent complaints in transformSetOperationTree() */
-	leftmostSelect->into = NULL;
+	leftmostSelect->intoClause = NULL;
 
 	/*
 	 * These are not one-time, exactly, but we want to process them here and
@@ -2533,7 +2554,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 	/*
 	 * Validity-check both leaf and internal SELECTs for disallowed ops.
 	 */
-	if (stmt->into)
+	if (stmt->intoClause)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT")));
@@ -3113,6 +3134,105 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
 }
 
 
+/*
+ * transformDeclareCursorStmt -
+ *	transform a DECLARE CURSOR Statement
+ *
+ * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
+ * significantly different from a SELECT) as far as parsing/rewriting/planning
+ * are concerned, but it's not passed to the executor and so in that sense is
+ * a utility statement.  We transform it into a Query exactly as if it were
+ * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
+ * field to carry the cursor name and options.
+ */
+static Query *
+transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
+{
+	Query	   *result;
+	List	   *extras_before = NIL,
+			   *extras_after = NIL;
+
+	/*
+	 * Don't allow both SCROLL and NO SCROLL to be specified
+	 */
+	if ((stmt->options & CURSOR_OPT_SCROLL) &&
+		(stmt->options & CURSOR_OPT_NO_SCROLL))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+				 errmsg("cannot specify both SCROLL and NO SCROLL")));
+
+	result = transformStmt(pstate, stmt->query,
+						   &extras_before, &extras_after);
+
+	/* Shouldn't get any extras, since grammar only allows SelectStmt */
+	if (extras_before || extras_after)
+		elog(ERROR, "unexpected extra stuff in cursor statement");
+	if (!IsA(result, Query) ||
+		result->commandType != CMD_SELECT ||
+		result->utilityStmt != NULL)
+		elog(ERROR, "unexpected non-SELECT command in cursor statement");
+
+	/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
+	if (result->intoClause)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+				 errmsg("DECLARE CURSOR cannot specify INTO")));
+
+	/* Implementation restriction (might go away someday) */
+	if (result->rowMarks != NIL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			  errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
+				 errdetail("Cursors must be READ ONLY.")));
+
+	/* We won't need the raw querytree any more */
+	stmt->query = NULL;
+
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
+
+/*
+ * transformExplainStmt -
+ *	transform an EXPLAIN Statement
+ *
+ * EXPLAIN is just like other utility statements in that we emit it as a
+ * CMD_UTILITY Query node with no transformation of the raw parse tree.
+ * However, if p_variableparams is set, it could be that the client is
+ * expecting us to resolve parameter types in something like
+ *		EXPLAIN SELECT * FROM tab WHERE col = $1
+ * To deal with such cases, we run parse analysis and throw away the result;
+ * this is a bit grotty but not worth contorting the rest of the system for.
+ * (The approach we use for DECLARE CURSOR won't work because the statement
+ * being explained isn't necessarily a SELECT, and in particular might rewrite
+ * to multiple parsetrees.)
+ */
+static Query *
+transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
+{
+	Query	   *result;
+
+	if (pstate->p_variableparams)
+	{
+		List	   *extras_before = NIL,
+				   *extras_after = NIL;
+
+		/* Since parse analysis scribbles on its input, copy the tree first! */
+		(void) transformStmt(pstate, copyObject(stmt->query),
+							 &extras_before, &extras_after);
+	}
+
+	/* Now return the untransformed command as a utility Query */
+	result = makeNode(Query);
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
+
 /* exported so planner can check again after rewriting, query pullup, etc */
 void
 CheckSelectLocking(Query *qry)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a69e6989a94956c1d584f32b3dce1810f4c44212..8884da228921cad401ebb63ffb634f546bdeb1e4 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.590 2007/04/26 16:13:11 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.591 2007/04/27 22:05:48 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -2355,12 +2355,12 @@ CreateAsStmt:
 					 * be attached to that Select's target list.
 					 */
 					SelectStmt *n = findLeftmostSelect((SelectStmt *) $6);
-					if (n->into != NULL)
+					if (n->intoClause != NULL)
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
 								 errmsg("CREATE TABLE AS cannot specify INTO")));
 					$4->rel->istemp = $2;
-					n->into = $4;
+					n->intoClause = $4;
 					$$ = $6;
 				}
 		;
@@ -5993,7 +5993,7 @@ simple_select:
 					SelectStmt *n = makeNode(SelectStmt);
 					n->distinctClause = $2;
 					n->targetList = $3;
-					n->into = $4;
+					n->intoClause = $4;
 					n->fromClause = $5;
 					n->whereClause = $6;
 					n->groupClause = $7;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 0c718adc32a5f7cd19b457179da58f5fc103c36b..4c1fb0cc4c12295c596193a9efeafe8478775edd 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.164 2007/02/01 19:10:27 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.165 2007/04/27 22:05:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -447,9 +447,10 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 	if (query == NULL || !IsA(query, Query))
 		elog(ERROR, "unexpected parse analysis result for subquery in FROM");
 
-	if (query->commandType != CMD_SELECT)
+	if (query->commandType != CMD_SELECT ||
+		query->utilityStmt != NULL)
 		elog(ERROR, "expected SELECT query from subquery in FROM");
-	if (query->into != NULL)
+	if (query->intoClause != NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("subquery in FROM cannot have SELECT INTO")));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d9a6af829621322ae4169f534a2f26fbb735b48f..a0f92f66a64ecf3eefac07f0c71b20c68ca83a09 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.216 2007/04/02 03:49:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.217 2007/04/27 22:05:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1117,7 +1117,8 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		elog(ERROR, "bad query in sub-select");
 	qtree = (Query *) linitial(qtrees);
 	if (qtree->commandType != CMD_SELECT ||
-		qtree->into != NULL)
+		qtree->utilityStmt != NULL ||
+		qtree->intoClause != NULL)
 		elog(ERROR, "bad query in sub-select");
 	sublink->subselect = (Node *) qtree;
 
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index ebb86f8bba914db11f07d915cbd9dd97933a2bb3..0263386d2a23de957e60a32a1f26085a720c6389 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.88 2007/04/02 03:49:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.89 2007/04/27 22:05:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -519,7 +519,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
 	if (stmt == NULL ||
 		!IsA(stmt, SelectStmt) ||
 		stmt->distinctClause != NIL ||
-		stmt->into != NULL ||
+		stmt->intoClause != NULL ||
 		stmt->fromClause != NIL ||
 		stmt->whereClause != NULL ||
 		stmt->groupClause != NIL ||
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 864b00f1e73554c208d4e37afb061fa9c21aca37..e57e2ab046cd1df06c71096d832e6190f342a56f 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.119 2007/03/19 23:38:29 wieck Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.120 2007/04/27 22:05:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -294,7 +294,9 @@ DefineQueryRewrite(char *rulename,
 		 */
 		query = (Query *) linitial(action);
 		if (!is_instead ||
-			query->commandType != CMD_SELECT || query->into != NULL)
+			query->commandType != CMD_SELECT ||
+			query->utilityStmt != NULL ||
+			query->intoClause != NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("rules on SELECT must have action INSTEAD SELECT")));
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index b54dea45dcaa87667d97ebca512068147762ae0b..58ed351c59e3a153e6233add0380aea10c8cbfce 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.115 2007/03/13 00:33:42 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.116 2007/04/27 22:05:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,7 +68,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 
 	qd->operation = plannedstmt->commandType;	/* operation */
 	qd->plannedstmt = plannedstmt;				/* plan */
-	qd->utilitystmt = NULL;
+	qd->utilitystmt = plannedstmt->utilityStmt;	/* in case DECLARE CURSOR */
 	qd->snapshot = snapshot;					/* snapshot */
 	qd->crosscheck_snapshot = crosscheck_snapshot;		/* RI check snapshot */
 	qd->dest = dest;			/* output dest */
@@ -257,7 +257,8 @@ ChoosePortalStrategy(List *stmts)
 			if (query->canSetTag)
 			{
 				if (query->commandType == CMD_SELECT &&
-					query->into == NULL)
+					query->utilityStmt == NULL &&
+					query->intoClause == NULL)
 					return PORTAL_ONE_SELECT;
 				if (query->commandType == CMD_UTILITY &&
 					query->utilityStmt != NULL)
@@ -276,7 +277,8 @@ ChoosePortalStrategy(List *stmts)
 			if (pstmt->canSetTag)
 			{
 				if (pstmt->commandType == CMD_SELECT &&
-					pstmt->into == NULL)
+					pstmt->utilityStmt == NULL &&
+					pstmt->intoClause == NULL)
 					return PORTAL_ONE_SELECT;
 			}
 		}
@@ -380,7 +382,8 @@ FetchStatementTargetList(Node *stmt)
 		else
 		{
 			if (query->commandType == CMD_SELECT &&
-				query->into == NULL)
+				query->utilityStmt == NULL &&
+				query->intoClause == NULL)
 				return query->targetList;
 			if (query->returningList)
 				return query->returningList;
@@ -392,7 +395,8 @@ FetchStatementTargetList(Node *stmt)
 		PlannedStmt *pstmt = (PlannedStmt *) stmt;
 
 		if (pstmt->commandType == CMD_SELECT &&
-			pstmt->into == NULL)
+			pstmt->utilityStmt == NULL &&
+			pstmt->intoClause == NULL)
 			return pstmt->planTree->targetlist;
 		if (pstmt->returningLists)
 			return (List *) linitial(pstmt->returningLists);
@@ -1222,7 +1226,8 @@ PortalRunMulti(Portal portal, bool isTopLevel,
 		 */
 		CHECK_FOR_INTERRUPTS();
 
-		if (IsA(stmt, PlannedStmt))
+		if (IsA(stmt, PlannedStmt) &&
+			((PlannedStmt *) stmt)->utilityStmt == NULL)
 		{
 			/*
 			 * process a plannable query.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9dd700ffe70c4c9b299c9e5c109cedd111537c7a..028cb47c7ac81b455511679eac2e6b7197809438 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.278 2007/04/26 16:13:12 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.279 2007/04/27 22:05:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -268,7 +268,7 @@ CommandIsReadOnly(Node *parsetree)
 		switch (stmt->commandType)
 		{
 			case CMD_SELECT:
-				if (stmt->into != NULL)
+				if (stmt->intoClause != NULL)
 					return false;	/* SELECT INTO */
 				else if (stmt->rowMarks != NIL)
 					return false;	/* SELECT FOR UPDATE/SHARE */
@@ -505,10 +505,20 @@ ProcessUtility(Node *parsetree,
 
 			/*
 			 * Portal (cursor) manipulation
+			 *
+			 * Note: DECLARE CURSOR is processed mostly as a SELECT, and
+			 * therefore what we will get here is a PlannedStmt not a bare
+			 * DeclareCursorStmt.
 			 */
-		case T_DeclareCursorStmt:
-			PerformCursorOpen((DeclareCursorStmt *) parsetree, params,
-							  queryString, isTopLevel);
+		case T_PlannedStmt:
+			{
+				PlannedStmt *stmt = (PlannedStmt *) parsetree;
+
+				if (stmt->utilityStmt == NULL ||
+					!IsA(stmt->utilityStmt, DeclareCursorStmt))
+					elog(ERROR, "non-DECLARE CURSOR PlannedStmt passed to ProcessUtility");
+				PerformCursorOpen(stmt, params, queryString, isTopLevel);
+			}
 			break;
 
 		case T_ClosePortalStmt:
@@ -1272,8 +1282,9 @@ QueryReturnsTuples(Query *parsetree)
 	switch (parsetree->commandType)
 	{
 		case CMD_SELECT:
-			/* returns tuples ... unless it's SELECT INTO */
-			if (parsetree->into == NULL)
+			/* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */
+			if (parsetree->utilityStmt == NULL &&
+				parsetree->intoClause == NULL)
 				return true;
 			break;
 		case CMD_INSERT:
@@ -1899,7 +1910,12 @@ CreateCommandTag(Node *parsetree)
 						 * will be useful for complaints about read-only
 						 * statements
 						 */
-						if (stmt->into != NULL)
+						if (stmt->utilityStmt != NULL)
+						{
+							Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+							tag = "DECLARE CURSOR";
+						}
+						else if (stmt->intoClause != NULL)
 							tag = "SELECT INTO";
 						else if (stmt->rowMarks != NIL)
 						{
@@ -1942,7 +1958,12 @@ CreateCommandTag(Node *parsetree)
 						 * will be useful for complaints about read-only
 						 * statements
 						 */
-						if (stmt->into != NULL)
+						if (stmt->utilityStmt != NULL)
+						{
+							Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
+							tag = "DECLARE CURSOR";
+						}
+						else if (stmt->intoClause != NULL)
 							tag = "SELECT INTO";
 						else if (stmt->rowMarks != NIL)
 						{
@@ -2009,7 +2030,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_SelectStmt:
-			if (((SelectStmt *) parsetree)->into)
+			if (((SelectStmt *) parsetree)->intoClause)
 				lev = LOGSTMT_DDL;		/* CREATE AS, SELECT INTO */
 			else
 				lev = LOGSTMT_ALL;
@@ -2330,10 +2351,10 @@ GetCommandLogLevel(Node *parsetree)
 				switch (stmt->commandType)
 				{
 					case CMD_SELECT:
-						if (stmt->into != NULL)
+						if (stmt->intoClause != NULL)
 							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
 						else
-							lev = LOGSTMT_ALL;
+							lev = LOGSTMT_ALL;	/* SELECT or DECLARE CURSOR */
 						break;
 
 					case CMD_UPDATE:
@@ -2359,10 +2380,10 @@ GetCommandLogLevel(Node *parsetree)
 				switch (stmt->commandType)
 				{
 					case CMD_SELECT:
-						if (stmt->into != NULL)
+						if (stmt->intoClause != NULL)
 							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
 						else
-							lev = LOGSTMT_ALL;
+							lev = LOGSTMT_ALL;	/* SELECT or DECLARE CURSOR */
 						break;
 
 					case CMD_UPDATE:
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index 3d774046136670793f9b5df4c49961f52967ef60..66a2effc5092c3017149aa32945d02c031073e24 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.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/commands/portalcmds.h,v 1.22 2007/03/13 00:33:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.23 2007/04/27 22:05:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,10 +15,11 @@
 #define PORTALCMDS_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
 #include "utils/portal.h"
 
 
-extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
+extern void PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
 							  const char *queryString, bool isTopLevel);
 
 extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 09cb4ff5031e58baad18b5987a3fc141eb03c4ed..179d7a765dbf2c2876a9a1e8f97a17aaaf2bd5ba 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.347 2007/04/26 16:13:14 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.348 2007/04/27 22:05:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -82,9 +82,11 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
  *
  *	  Utility statements (i.e. non-optimizable statements) have the
  *	  utilityStmt field set, and the Query itself is mostly dummy.
+ *	  DECLARE CURSOR is a special case: it is represented like a SELECT,
+ *	  but the original DeclareCursorStmt is stored in utilityStmt.
  *
  *	  Planning converts a Query tree into a Plan tree headed by a PlannedStmt
- *	  noded --- the Query structure is not used by the executor.
+ *	  node --- the Query structure is not used by the executor.
  */
 typedef struct Query
 {
@@ -96,13 +98,13 @@ typedef struct Query
 
 	bool		canSetTag;		/* do I set the command result tag? */
 
-	Node	   *utilityStmt;	/* non-null if this is a non-optimizable
-								 * statement */
+	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR or a
+								 * non-optimizable statement */
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
-	IntoClause *into;			/* target for SELECT INTO / CREATE TABLE AS */
+	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -732,7 +734,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
-	IntoClause *into;			/* target for SELECT INTO / CREATE TABLE AS */
+	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
 	Node	   *whereClause;	/* WHERE qualification */
@@ -1427,6 +1429,10 @@ typedef struct CommentStmt
 
 /* ----------------------
  *		Declare Cursor Statement
+ *
+ * Note: the "query" field of DeclareCursorStmt is only used in the raw grammar
+ * output.  After parse analysis it's set to null, and the Query points to the
+ * DeclareCursorStmt, not vice versa.
  * ----------------------
  */
 #define CURSOR_OPT_BINARY		0x0001		/* BINARY */
@@ -1441,7 +1447,7 @@ typedef struct DeclareCursorStmt
 	NodeTag		type;
 	char	   *portalname;		/* name of the portal (cursor) */
 	int			options;		/* bitmask of options (see above) */
-	Node	   *query;			/* the SELECT query */
+	Node	   *query;			/* the raw SELECT query */
 } DeclareCursorStmt;
 
 /* ----------------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aecfb519399710f88f7ac1343e75c4dfe432a3c0..9d336e4b889d30ebfaa88ba8dd803236664c513e 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.93 2007/02/27 01:11:26 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.94 2007/04/27 22:05:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,7 +46,9 @@ typedef struct PlannedStmt
 	/* 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 */
+	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR */
+
+	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
 
 	List	   *subplans;		/* Plan trees for SubPlan expressions */