diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml
index 77c45d90b09e409d6e00bd6ee2b19cca332d5461..5f4812200710d73f48e081907b65437d05cd909f 100644
--- a/doc/src/sgml/ref/declare.sgml
+++ b/doc/src/sgml/ref/declare.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/declare.sgml,v 1.18 2002/05/18 15:44:47 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/declare.sgml,v 1.19 2003/03/10 03:53:48 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -47,8 +47,7 @@ DECLARE <replaceable class="parameter">cursorname</replaceable> [ BINARY ] [ INS
       <term>BINARY</term>
       <listitem>
        <para>
-	Causes the cursor to fetch data in binary
-	rather than in text format.
+	Causes the cursor to return data in binary rather than in text format.
        </para>
       </listitem>
      </varlistentry>
@@ -70,9 +69,8 @@ DECLARE <replaceable class="parameter">cursorname</replaceable> [ BINARY ] [ INS
       <term>SCROLL</term>
       <listitem>
        <para>
-	<acronym>SQL92</acronym> keyword indicating that data may be retrieved
-	in multiple rows per FETCH operation. Since this is allowed at all times
-	by <productname>PostgreSQL</productname> this keyword has no effect.
+	Specifies that the cursor may be used to retrieve rows
+	in a nonsequential fashion (e.g., backwards).
        </para>
       </listitem>
      </varlistentry>
@@ -81,10 +79,10 @@ DECLARE <replaceable class="parameter">cursorname</replaceable> [ BINARY ] [ INS
       <term><replaceable class="parameter">query</replaceable></term>
       <listitem>
        <para>
-	An SQL query which will provide the rows to be governed by the
-	cursor.
-	Refer to the SELECT statement for further information about
-	valid arguments.
+	A <command>SELECT</> query which will provide the rows to be
+	returned by the cursor.
+	Refer to <xref linkend="sql-select" endterm="sql-select-title">
+	for further information about valid arguments.
        </para>
       </listitem>
      </varlistentry>
@@ -126,6 +124,10 @@ DECLARE <replaceable class="parameter">cursorname</replaceable> [ BINARY ] [ INS
 
     </variablelist>
    </para>
+
+   <para>
+    The BINARY, INSENSITIVE, and SCROLL keywords may appear in any order.
+   </para>
   </refsect2>
 
   <refsect2 id="R2-SQL-DECLARE-2">
@@ -193,9 +195,8 @@ ERROR:  DECLARE CURSOR may only be used in begin/end transaction blocks
   </para>
 
   <para>
-   Normal cursors return data in text format, either ASCII or another
-   encoding scheme depending on how the <productname>PostgreSQL</productname>
-   backend was built. Since
+   Normal cursors return data in text format, the same as a <command>SELECT</>
+   would produce.  Since
    data is stored natively in binary format, the system must
    do a conversion to produce the text format. In addition,
    text formats are often larger in size than the corresponding binary format.
@@ -228,15 +229,11 @@ ERROR:  DECLARE CURSOR may only be used in begin/end transaction blocks
    representations (e.g., <quote>big-endian</quote> versus <quote>little-endian</quote>),
    you will probably not want your data returned in
    binary format.
-   However, binary cursors may be a
-   little more efficient since there is less conversion overhead in
-   the server to client data transfer.
 
    <tip>
     <para>
-     If you intend to display the data in
-     ASCII,  getting it back in ASCII will save you some
-     effort on the client side.
+     If you intend to display the data as text, retrieving it in text form
+     will save you some effort on the client side.
     </para>
    </tip>
   </para>
@@ -250,7 +247,7 @@ ERROR:  DECLARE CURSOR may only be used in begin/end transaction blocks
    </title>
 
    <para>
-    Cursors are only available in transactions. Use to
+    Cursors are only available within transactions. Use
     <xref linkend="sql-begin" endterm="sql-begin-title">,
     <xref linkend="sql-commit" endterm="sql-commit-title">
     and
@@ -258,6 +255,15 @@ ERROR:  DECLARE CURSOR may only be used in begin/end transaction blocks
     to define a transaction block.
    </para>
 
+   <para>
+    The <literal>SCROLL</> option should be specified when defining a cursor 
+    that will be used to fetch backwards.  This is required by
+    <acronym>SQL92</acronym>.  However, for compatibility with
+    earlier versions, <productname>PostgreSQL</productname> will allow
+    backward fetches without <literal>SCROLL</>, if the cursor's query plan
+    is simple enough that no extra overhead is needed to support it.
+   </para>
+
    <para>
     In <acronym>SQL92</acronym> cursors are only available in
     embedded <acronym>SQL</acronym> (<acronym>ESQL</acronym>) applications. 
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 14639cc268ce0e75c345f24aad7c63b21dca42d7..be812bebf294bc877040e21ab9120be86600a378 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.23 2003/02/02 23:46:37 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.24 2003/03/10 03:53:49 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -56,7 +56,8 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="PARAMETER">query</replaceabl
       <listitem>
        <para>
 	Any <command>SELECT</>, <command>INSERT</>, <command>UPDATE</>,
-	<command>DELETE</>, or <command>EXECUTE</> query.
+	<command>DELETE</>, <command>EXECUTE</>,
+	or <command>DECLARE CURSOR</> query.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/fetch.sgml b/doc/src/sgml/ref/fetch.sgml
index 4770545cb838379e82d62bb3d6b3bdcaa2f80e42..0452cf0144ffb0a76778ab8332835237d022c32a 100644
--- a/doc/src/sgml/ref/fetch.sgml
+++ b/doc/src/sgml/ref/fetch.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/fetch.sgml,v 1.25 2003/02/04 11:23:58 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/fetch.sgml,v 1.26 2003/03/10 03:53:49 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -13,7 +13,7 @@ PostgreSQL documentation
    FETCH
   </refname>
   <refpurpose>
-   retrieve rows from a table using a cursor
+   retrieve rows from a query using a cursor
   </refpurpose>
  </refnamediv>
  <refsynopsisdiv>
@@ -66,7 +66,7 @@ FETCH [ FORWARD | BACKWARD | RELATIVE ] [ <replaceable class="PARAMETER">#</repl
 	  <term>RELATIVE</term>
 	  <listitem>
 	   <para>
-	    Noise word for SQL92 compatibility.
+	    Same as FORWARD; provided for SQL92 compatibility.
 	   </para>
 	  </listitem>
 	 </varlistentry>
@@ -247,13 +247,20 @@ WARNING:  FETCH/ABSOLUTE not supported, using RELATIVE
    </title>
 
    <para>
-    Note that the FORWARD, BACKWARD, and ALL keywords are
+    A cursor to be used in backwards fetching should be declared with the
+    SCROLL option.  In simple cases, <productname>PostgreSQL</productname>
+    will allow backwards fetch from cursors not declared with SCROLL, but
+    this behavior is best not relied on.
+   </para>
+
+   <para>
+    The FORWARD, BACKWARD, and ALL keywords are
     <productname>PostgreSQL</productname> extensions.
     See below for details on compatibility issues.
    </para>
 
    <para>
-    Updating data in a cursor is not supported by 
+    Updating data via a cursor is not supported by 
     <productname>PostgreSQL</productname>,
     because mapping cursor updates back to base tables is
     not generally possible, as is also the case with VIEW updates.
@@ -262,8 +269,7 @@ WARNING:  FETCH/ABSOLUTE not supported, using RELATIVE
    </para>
 
    <para>
-    Cursors may only be used inside of transactions because
-    the data that they store spans multiple user queries.
+    Cursors may only be used inside transaction blocks.
    </para>
 
    <para>
@@ -288,7 +294,7 @@ WARNING:  FETCH/ABSOLUTE not supported, using RELATIVE
   </title>
 
   <para>
-   The following examples traverses a table using a cursor.
+   The following example traverses a table using a cursor.
 
 <programlisting>
 -- Set up and use a cursor:
diff --git a/doc/src/sgml/ref/move.sgml b/doc/src/sgml/ref/move.sgml
index 69be788c35c5fb12641f64c8b4e19db91c972faa..928faabc818bcee4c6696e2e5cf6842a0c3e513a 100644
--- a/doc/src/sgml/ref/move.sgml
+++ b/doc/src/sgml/ref/move.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/move.sgml,v 1.18 2003/02/04 11:23:58 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/move.sgml,v 1.19 2003/03/10 03:53:49 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -88,13 +88,11 @@ DECLARE liahona CURSOR  FOR SELECT * FROM films;
 -- Skip first 5 rows:
 MOVE FORWARD 5 IN liahona;
 <computeroutput>
-MOVE
+MOVE 5
 </computeroutput>
 -- Fetch 6th row in the cursor liahona:
 FETCH 1 IN liahona;
 <computeroutput>
-FETCH
-
  code  | title  | did | date_prod | kind   | len
 -------+--------+-----+-----------+--------+-------
  P_303 | 48 Hrs | 103 | 1982-10-22| Action | 01:37
diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml
index 418bd83ace222495d9febd0f50ea2e240c5b69dc..754017309156856916c8852f697b3295058877d6 100644
--- a/doc/src/sgml/ref/prepare.sgml
+++ b/doc/src/sgml/ref/prepare.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/prepare.sgml,v 1.2 2003/02/02 23:46:37 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/prepare.sgml,v 1.3 2003/03/10 03:53:49 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -54,6 +54,15 @@ PostgreSQL documentation
        </para>
       </listitem>
      </varlistentry>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">query</replaceable></term>
+      <listitem>
+       <para>
+	Any <command>SELECT</>, <command>INSERT</>, <command>UPDATE</>,
+	or <command>DELETE</> query.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </refsect2>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8bc39884b43dc53651d43e1d9b68ba9ca600f185..11425620e6b22974dc135fa23a3e8774a69e9b62 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
- *	  $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.103 2003/02/10 17:06:23 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.104 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -85,7 +85,9 @@ ExplainQuery(ExplainStmt *stmt, CommandDest dest)
 	if (query->commandType == CMD_UTILITY)
 	{
 		/* Rewriter will not cope with utility statements */
-		if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
+		if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
+			ExplainOneQuery(query, stmt, tstate);
+		else if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
 			ExplainExecuteQuery(stmt, tstate);
 		else
 			do_text_output_oneline(tstate, "Utility statements have no plan structure");
@@ -125,30 +127,45 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
 {
 	Plan	   *plan;
 	QueryDesc  *queryDesc;
+	bool		isCursor = false;
+	int			cursorOptions = 0;
 
 	/* planner will not cope with utility statements */
 	if (query->commandType == CMD_UTILITY)
 	{
-		if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
+		if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
+		{
+			DeclareCursorStmt *dcstmt;
+			List	   *rewritten;
+
+			dcstmt = (DeclareCursorStmt *) query->utilityStmt;
+			query = (Query *) dcstmt->query;
+			isCursor = true;
+			cursorOptions = dcstmt->options;
+			/* Still need to rewrite cursor command */
+			Assert(query->commandType == CMD_SELECT);
+			rewritten = QueryRewrite(query);
+			if (length(rewritten) != 1)
+				elog(ERROR, "ExplainOneQuery: unexpected rewrite result");
+			query = (Query *) lfirst(rewritten);
+			Assert(query->commandType == CMD_SELECT);
+			/* do not actually execute the underlying query! */
+			stmt->analyze = false;
+		}
+		else if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
+		{
 			do_text_output_oneline(tstate, "NOTIFY");
+			return;
+		}
 		else
+		{
 			do_text_output_oneline(tstate, "UTILITY");
-		return;
+			return;
+		}
 	}
 
-	/*
-	 * We don't support DECLARE CURSOR in EXPLAIN, but parser will take it
-	 * because it's an OptimizableStmt
-	 */
-	if (query->isPortal)
-		elog(ERROR, "EXPLAIN / DECLARE CURSOR is not supported");
-
 	/* plan the query */
-	plan = planner(query);
-
-	/* pg_plan could have failed */
-	if (plan == NULL)
-		return;
+	plan = planner(query, isCursor, cursorOptions);
 
 	/* Create a QueryDesc requesting no output */
 	queryDesc = CreateQueryDesc(query, plan, None, NULL, NULL,
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 5881fe6c582288f74b894de41d2389b433ab1f11..0621cdd59036df38dc9acffc72e457a5a3927db7 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.8 2003/01/08 00:22:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.9 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,39 +19,88 @@
 
 #include "commands/portalcmds.h"
 #include "executor/executor.h"
+#include "optimizer/planner.h"
+#include "rewrite/rewriteHandler.h"
+
+
+static Portal PreparePortal(char *portalName);
 
 
 /*
- * PortalCleanup
- *
- * Clean up a portal when it's dropped.  Since this mainly exists to run
- * ExecutorEnd(), it should not be set as the cleanup hook until we have
- * called ExecutorStart() on the portal's query.
+ * PerformCursorOpen
+ *		Execute SQL DECLARE CURSOR command.
  */
 void
-PortalCleanup(Portal portal)
+PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
 {
+	List	   *rewritten;
+	Query	   *query;
+	Plan	   *plan;
+	Portal		portal;
+	MemoryContext oldContext;
+	char	   *cursorName;
+	QueryDesc  *queryDesc;
+
+	/* Check for invalid context (must be in transaction block) */
+	RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
+
 	/*
-	 * sanity checks
+	 * The query has been through parse analysis, but not rewriting or
+	 * planning as yet.  Note that the grammar ensured we have a SELECT
+	 * query, so we are not expecting rule rewriting to do anything strange.
 	 */
-	AssertArg(PortalIsValid(portal));
-	AssertArg(portal->cleanup == PortalCleanup);
+	rewritten = QueryRewrite((Query *) stmt->query);
+	if (length(rewritten) != 1 || !IsA(lfirst(rewritten), Query))
+		elog(ERROR, "PerformCursorOpen: unexpected rewrite result");
+	query = (Query *) lfirst(rewritten);
+	if (query->commandType != CMD_SELECT)
+		elog(ERROR, "PerformCursorOpen: unexpected rewrite result");
+
+	if (query->into)
+		elog(ERROR, "DECLARE CURSOR may not specify INTO");
+	if (query->rowMarks != NIL)
+		elog(ERROR, "DECLARE/UPDATE is not supported"
+			 "\n\tCursors must be READ ONLY");
+
+	plan = planner(query, true, stmt->options);
+
+	/* If binary cursor, switch to alternate output format */
+	if ((stmt->options & CURSOR_OPT_BINARY) && dest == Remote)
+		dest = RemoteInternal;
 
 	/*
-	 * tell the executor to shutdown the query
+	 * Create a portal and copy the query and plan into its memory context.
 	 */
-	ExecutorEnd(PortalGetQueryDesc(portal));
+	portal = PreparePortal(stmt->portalname);
+
+	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+	query = copyObject(query);
+	plan = copyObject(plan);
 
 	/*
-	 * This should be unnecessary since the querydesc should be in the
-	 * portal's memory context, but do it anyway for symmetry.
+	 * Create the QueryDesc object in the portal context, too.
 	 */
-	FreeQueryDesc(PortalGetQueryDesc(portal));
-}
+	cursorName = pstrdup(stmt->portalname);
+	queryDesc = CreateQueryDesc(query, plan, dest, cursorName, NULL, false);
+
+	/*
+	 * call ExecStart to prepare the plan for execution
+	 */
+	ExecutorStart(queryDesc);
 
+	/* Arrange to shut down the executor if portal is dropped */
+	PortalSetQuery(portal, queryDesc, PortalCleanup);
+
+	/*
+	 * We're done; the query won't actually be run until PerformPortalFetch
+	 * is called.
+	 */
+	MemoryContextSwitchTo(oldContext);
+}
 
 /*
  * PerformPortalFetch
+ *		Execute SQL FETCH or MOVE command.
  *
  *	name: name of portal
  *	forward: forward or backward fetch?
@@ -70,28 +119,20 @@ PerformPortalFetch(char *name,
 				   char *completionTag)
 {
 	Portal		portal;
-	QueryDesc  *queryDesc;
-	EState	   *estate;
-	MemoryContext oldcontext;
-	ScanDirection direction;
-	bool		temp_desc = false;
+	long		nprocessed;
 
 	/* initialize completion status in case of early exit */
 	if (completionTag)
 		strcpy(completionTag, (dest == None) ? "MOVE 0" : "FETCH 0");
 
-	/*
-	 * sanity checks
-	 */
+	/* sanity checks */
 	if (name == NULL)
 	{
 		elog(WARNING, "PerformPortalFetch: missing portal name");
 		return;
 	}
 
-	/*
-	 * get the portal from the portal name
-	 */
+	/* get the portal from the portal name */
 	portal = GetPortalByName(name);
 	if (!PortalIsValid(portal))
 	{
@@ -100,6 +141,31 @@ PerformPortalFetch(char *name,
 		return;
 	}
 
+	/* Do it */
+	nprocessed = DoPortalFetch(portal, forward, count, dest);
+
+	/* Return command status if wanted */
+	if (completionTag)
+		snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %ld",
+				 (dest == None) ? "MOVE" : "FETCH",
+				 nprocessed);
+}
+
+/*
+ * DoPortalFetch
+ *		Guts of PerformPortalFetch --- shared with SPI cursor operations
+ *
+ * Returns number of rows processed.
+ */
+long
+DoPortalFetch(Portal portal, bool forward, long count, CommandDest dest)
+{
+	QueryDesc  *queryDesc;
+	EState	   *estate;
+	MemoryContext oldcontext;
+	ScanDirection direction;
+	bool		temp_desc = false;
+
 	/*
 	 * Zero count means to re-fetch the current row, if any (per SQL92)
 	 */
@@ -113,9 +179,7 @@ PerformPortalFetch(char *name,
 		if (dest == None)
 		{
 			/* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
-			if (completionTag && on_row)
-				strcpy(completionTag, "MOVE 1");
-			return;
+			return on_row ? 1L : 0L;
 		}
 		else
 		{
@@ -128,9 +192,9 @@ PerformPortalFetch(char *name,
 			 */
 			if (on_row)
 			{
-				PerformPortalFetch(name, false /* backward */, 1L,
-								   None, /* throw away output */
-								   NULL /* do not modify the command tag */);
+				DoPortalFetch(portal,
+							  false /* backward */, 1L,
+							  None /* throw away output */);
 				/* Set up to fetch one row forward */
 				count = 1;
 				forward = true;
@@ -202,6 +266,10 @@ PerformPortalFetch(char *name,
 	}
 	else
 	{
+		if (!portal->backwardOK)
+			elog(ERROR, "Cursor cannot scan backwards"
+				 "\n\tDeclare it with SCROLL option to enable backward scan");
+
 		if (portal->atStart || count == 0)
 			direction = NoMovementScanDirection;
 		else
@@ -222,12 +290,6 @@ PerformPortalFetch(char *name,
 		}
 	}
 
-	/* Return command status if wanted */
-	if (completionTag)
-		snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %u",
-				 (dest == None) ? "MOVE" : "FETCH",
-				 estate->es_processed);
-
 	/*
 	 * Clean up and switch back to old context.
 	 */
@@ -235,18 +297,21 @@ PerformPortalFetch(char *name,
 		pfree(queryDesc);
 
 	MemoryContextSwitchTo(oldcontext);
+
+	return estate->es_processed;
 }
 
 /*
  * PerformPortalClose
+ *		Close a cursor.
  */
 void
-PerformPortalClose(char *name, CommandDest dest)
+PerformPortalClose(char *name)
 {
 	Portal		portal;
 
 	/*
-	 * sanity checks
+	 * sanity checks ... why is this case allowed by the grammar, anyway?
 	 */
 	if (name == NULL)
 	{
@@ -270,3 +335,64 @@ PerformPortalClose(char *name, CommandDest dest)
 	 */
 	PortalDrop(portal);
 }
+
+
+/*
+ * PreparePortal
+ */
+static Portal
+PreparePortal(char *portalName)
+{
+	Portal		portal;
+
+	/*
+	 * Check for already-in-use portal name.
+	 */
+	portal = GetPortalByName(portalName);
+	if (PortalIsValid(portal))
+	{
+		/*
+		 * XXX Should we raise an error rather than closing the old
+		 * portal?
+		 */
+		elog(WARNING, "Closing pre-existing portal \"%s\"",
+			 portalName);
+		PortalDrop(portal);
+	}
+
+	/*
+	 * Create the new portal.
+	 */
+	portal = CreatePortal(portalName);
+
+	return portal;
+}
+
+
+/*
+ * PortalCleanup
+ *
+ * Clean up a portal when it's dropped.  Since this mainly exists to run
+ * ExecutorEnd(), it should not be set as the cleanup hook until we have
+ * called ExecutorStart() on the portal's query.
+ */
+void
+PortalCleanup(Portal portal)
+{
+	/*
+	 * sanity checks
+	 */
+	AssertArg(PortalIsValid(portal));
+	AssertArg(portal->cleanup == PortalCleanup);
+
+	/*
+	 * tell the executor to shutdown the query
+	 */
+	ExecutorEnd(PortalGetQueryDesc(portal));
+
+	/*
+	 * This should be unnecessary since the querydesc should be in the
+	 * portal's memory context, but do it anyway for symmetry.
+	 */
+	FreeQueryDesc(PortalGetQueryDesc(portal));
+}
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index b22ad7634982003f66f721dea37dc7bcf2079cd6..b189e3e94bc521d7d89168661dad9df103ef763d 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/executor/execAmi.c,v 1.69 2003/02/09 00:30:39 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/executor/execAmi.c,v 1.70 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -281,3 +281,61 @@ ExecSupportsMarkRestore(NodeTag plantype)
 
 	return false;
 }
+
+/*
+ * ExecSupportsBackwardScan - does a plan type support backwards scanning?
+ *
+ * Ideally, all plan types would support backwards scan, but that seems
+ * unlikely to happen soon.  In some cases, a plan node passes the backwards
+ * scan down to its children, and so supports backwards scan only if its
+ * children do.  Therefore, this routine must be passed a complete plan tree.
+ */
+bool
+ExecSupportsBackwardScan(Plan *node)
+{
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Result:
+			if (outerPlan(node) != NULL)
+				return ExecSupportsBackwardScan(outerPlan(node));
+			else
+				return false;
+
+		case T_Append:
+		{
+			List   *l;
+
+			foreach(l, ((Append *) node)->appendplans)
+			{
+				if (!ExecSupportsBackwardScan((Plan *) lfirst(l)))
+					return false;
+			}
+			return true;
+		}
+
+		case T_SeqScan:
+		case T_IndexScan:
+		case T_TidScan:
+		case T_FunctionScan:
+			return true;
+
+		case T_SubqueryScan:
+			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan);
+
+		case T_Material:
+		case T_Sort:
+			return true;
+
+		case T_Unique:
+			return ExecSupportsBackwardScan(outerPlan(node));
+
+		case T_Limit:
+			return ExecSupportsBackwardScan(outerPlan(node));
+
+		default:
+			return false;
+	}
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9c4b6e748192b4bf28696b0a08dd3dc72cfc047f..f037e72fd911bba0fe2f458ab09cd35800750abf 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.200 2003/02/03 15:07:06 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.201 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -537,9 +537,7 @@ InitPlan(QueryDesc *queryDesc)
 	 */
 	do_select_into = false;
 
-	if (operation == CMD_SELECT &&
-		!parseTree->isPortal &&
-		parseTree->into != NULL)
+	if (operation == CMD_SELECT && parseTree->into != NULL)
 	{
 		do_select_into = true;
 		/*
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index d94e12cde5619fd119d79b6b402eb7c4a6fd4b86..e1ccdf08f97425bb69540364c59eef749e15feed 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.86 2003/02/14 21:12:45 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.87 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -725,9 +725,7 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 
 	if (queryTree->commandType != CMD_SELECT)
 		elog(ERROR, "plan in SPI_cursor_open() is not a SELECT");
-	if (queryTree->isPortal)
-		elog(ERROR, "plan in SPI_cursor_open() must NOT be a DECLARE already");
-	else if (queryTree->into != NULL)
+	if (queryTree->into != NULL)
 		elog(ERROR, "plan in SPI_cursor_open() must NOT be a SELECT INTO");
 
 	/* Increment CommandCounter to see changes made by now */
@@ -764,20 +762,12 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 
 	/* Create the portal */
 	portal = CreatePortal(name);
-	if (portal == NULL)
-		elog(ERROR, "failed to create portal \"%s\"", name);
 
 	/* Switch to portals memory and copy the parsetree and plan to there */
 	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 	queryTree = copyObject(queryTree);
 	planTree = copyObject(planTree);
 
-	/* Modify the parsetree to be a cursor */
-	queryTree->isPortal = true;
-	queryTree->into = makeNode(RangeVar);
-	queryTree->into->relname = pstrdup(name);
-	queryTree->isBinary = false;
-
 	/* If the plan has parameters, set them up */
 	if (spiplan->nargs > 0)
 	{
@@ -812,7 +802,7 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 		paramLI = NULL;
 
 	/* Create the QueryDesc object */
-	queryDesc = CreateQueryDesc(queryTree, planTree, SPI, NULL,
+	queryDesc = CreateQueryDesc(queryTree, planTree, SPI, pstrdup(name),
 								paramLI, false);
 
 	/* Start the executor */
@@ -1106,7 +1096,8 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 					if (stmt->filename == NULL)
 						return SPI_ERROR_COPY;
 				}
-				else if (IsA(queryTree->utilityStmt, ClosePortalStmt) ||
+				else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
+						 IsA(queryTree->utilityStmt, ClosePortalStmt) ||
 						 IsA(queryTree->utilityStmt, FetchStmt))
 					return SPI_ERROR_CURSOR;
 				else if (IsA(queryTree->utilityStmt, TransactionStmt))
@@ -1263,12 +1254,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 static int
 _SPI_pquery(QueryDesc *queryDesc, bool runit, int tcount)
 {
-	Query	   *parseTree = queryDesc->parsetree;
 	int			operation = queryDesc->operation;
-	CommandDest dest = queryDesc->dest;
-	bool		isRetrieveIntoPortal = false;
-	bool		isRetrieveIntoRelation = false;
-	char	   *intoName = NULL;
 	int			res;
 	Oid			save_lastoid;
 
@@ -1276,20 +1262,10 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, int tcount)
 	{
 		case CMD_SELECT:
 			res = SPI_OK_SELECT;
-			if (parseTree->isPortal)
-			{
-				isRetrieveIntoPortal = true;
-				intoName = parseTree->into->relname;
-				parseTree->isBinary = false;	/* */
-
-				return SPI_ERROR_CURSOR;
-
-			}
-			else if (parseTree->into != NULL)	/* select into table */
+			if (queryDesc->parsetree->into != NULL)	/* select into table */
 			{
 				res = SPI_OK_SELINTO;
-				isRetrieveIntoRelation = true;
-				queryDesc->dest = None; /* */
+				queryDesc->dest = None; /* don't output results anywhere */
 			}
 			break;
 		case CMD_INSERT:
@@ -1315,14 +1291,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, int tcount)
 
 	ExecutorStart(queryDesc);
 
-	/*
-	 * Don't work currently --- need to rearrange callers so that we
-	 * prepare the portal before doing ExecutorStart() etc. See
-	 * pquery.c for the correct order of operations.
-	 */
-	if (isRetrieveIntoPortal)
-		elog(FATAL, "SPI_select: retrieve into portal not implemented");
-
 	ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
 
 	_SPI_current->processed = queryDesc->estate->es_processed;
@@ -1334,7 +1302,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, int tcount)
 			elog(FATAL, "SPI_select: # of processed tuples check failed");
 	}
 
-	if (dest == SPI)
+	if (queryDesc->dest == SPI)
 	{
 		SPI_processed = _SPI_current->processed;
 		SPI_lastoid = save_lastoid;
@@ -1367,12 +1335,6 @@ static void
 _SPI_cursor_operation(Portal portal, bool forward, int count,
 					  CommandDest dest)
 {
-	QueryDesc  *querydesc;
-	EState	   *estate;
-	MemoryContext oldcontext;
-	ScanDirection direction;
-	CommandDest olddest;
-
 	/* Check that the portal is valid */
 	if (!PortalIsValid(portal))
 		elog(ERROR, "invalid portal in SPI cursor operation");
@@ -1386,53 +1348,9 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
 	_SPI_current->processed = 0;
 	_SPI_current->tuptable = NULL;
 
-	/* Switch to the portals memory context */
-	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-
-	querydesc = PortalGetQueryDesc(portal);
-	estate = querydesc->estate;
-
-	/* Save the queries command destination and set it to SPI (for fetch) */
-	/* or None (for move) */
-	olddest = querydesc->dest;
-	querydesc->dest = dest;
-
-	/* Run the executor like PerformPortalFetch and remember states */
-	if (forward)
-	{
-		if (portal->atEnd)
-			direction = NoMovementScanDirection;
-		else
-			direction = ForwardScanDirection;
-
-		ExecutorRun(querydesc, direction, (long) count);
-
-		if (estate->es_processed > 0)
-			portal->atStart = false;	/* OK to back up now */
-		if (count <= 0 || (int) estate->es_processed < count)
-			portal->atEnd = true;		/* we retrieved 'em all */
-	}
-	else
-	{
-		if (portal->atStart)
-			direction = NoMovementScanDirection;
-		else
-			direction = BackwardScanDirection;
-
-		ExecutorRun(querydesc, direction, (long) count);
-
-		if (estate->es_processed > 0)
-			portal->atEnd = false;		/* OK to go forward now */
-		if (count <= 0 || (int) estate->es_processed < count)
-			portal->atStart = true;		/* we retrieved 'em all */
-	}
-
-	_SPI_current->processed = estate->es_processed;
-
-	/* Restore the old command destination and switch back to callers */
-	/* memory context */
-	querydesc->dest = olddest;
-	MemoryContextSwitchTo(oldcontext);
+	/* Run the cursor */
+	_SPI_current->processed = DoPortalFetch(portal, forward, (long) count,
+											dest);
 
 	if (dest == SPI && _SPI_checktuples())
 		elog(FATAL, "SPI_fetch: # of processed tuples check failed");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 627f62c84f8c27d6cc541ca11ce9b0657fd27e09..9ea51d589b5d05b00cc8d43581c337596c0ca001 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
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.245 2003/03/05 20:01:01 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.246 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1465,8 +1465,6 @@ _copyQuery(Query *from)
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
 	COPY_NODE_FIELD(into);
-	COPY_SCALAR_FIELD(isPortal);
-	COPY_SCALAR_FIELD(isBinary);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasSubLinks);
 	COPY_NODE_FIELD(rtable);
@@ -1547,8 +1545,6 @@ _copySelectStmt(SelectStmt *from)
 	COPY_NODE_FIELD(groupClause);
 	COPY_NODE_FIELD(havingClause);
 	COPY_NODE_FIELD(sortClause);
-	COPY_STRING_FIELD(portalname);
-	COPY_SCALAR_FIELD(binary);
 	COPY_NODE_FIELD(limitOffset);
 	COPY_NODE_FIELD(limitCount);
 	COPY_NODE_FIELD(forUpdate);
@@ -1648,6 +1644,17 @@ _copyInsertDefault(InsertDefault *from)
 	return newnode;
 }
 
+static DeclareCursorStmt *
+_copyDeclareCursorStmt(DeclareCursorStmt *from)
+{
+	DeclareCursorStmt *newnode = makeNode(DeclareCursorStmt);
+
+	COPY_STRING_FIELD(portalname);
+	COPY_SCALAR_FIELD(options);
+	COPY_NODE_FIELD(query);
+
+	return newnode;
+}
 
 static ClosePortalStmt *
 _copyClosePortalStmt(ClosePortalStmt *from)
@@ -2632,6 +2639,9 @@ copyObject(void *from)
 		case T_GrantStmt:
 			retval = _copyGrantStmt(from);
 			break;
+		case T_DeclareCursorStmt:
+			retval = _copyDeclareCursorStmt(from);
+			break;
 		case T_ClosePortalStmt:
 			retval = _copyClosePortalStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6a0f36600979976a32290728e51ef66745ec5dee..3dd552bdd523bcc6fdea845b135e5ce7cc9ae28f 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
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.188 2003/03/05 20:01:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.189 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -560,8 +560,6 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
 	COMPARE_NODE_FIELD(into);
-	COMPARE_SCALAR_FIELD(isPortal);
-	COMPARE_SCALAR_FIELD(isBinary);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
 	COMPARE_NODE_FIELD(rtable);
@@ -631,8 +629,6 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 	COMPARE_NODE_FIELD(groupClause);
 	COMPARE_NODE_FIELD(havingClause);
 	COMPARE_NODE_FIELD(sortClause);
-	COMPARE_STRING_FIELD(portalname);
-	COMPARE_SCALAR_FIELD(binary);
 	COMPARE_NODE_FIELD(limitOffset);
 	COMPARE_NODE_FIELD(limitCount);
 	COMPARE_NODE_FIELD(forUpdate);
@@ -718,6 +714,16 @@ _equalInsertDefault(InsertDefault *a, InsertDefault *b)
 	return true;
 }
 
+static bool
+_equalDeclareCursorStmt(DeclareCursorStmt *a, DeclareCursorStmt *b)
+{
+	COMPARE_STRING_FIELD(portalname);
+	COMPARE_SCALAR_FIELD(options);
+	COMPARE_NODE_FIELD(query);
+
+	return true;
+}
+
 static bool
 _equalClosePortalStmt(ClosePortalStmt *a, ClosePortalStmt *b)
 {
@@ -1756,6 +1762,9 @@ equal(void *a, void *b)
 		case T_GrantStmt:
 			retval = _equalGrantStmt(a, b);
 			break;
+		case T_DeclareCursorStmt:
+			retval = _equalDeclareCursorStmt(a, b);
+			break;
 		case T_ClosePortalStmt:
 			retval = _equalClosePortalStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8485244492cab297276ac95276985fda06be313c..899b93e4727944bdce290cdbc4319d5b68d4561f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.200 2003/02/16 02:30:37 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.201 2003/03/10 03:53:49 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1081,6 +1081,16 @@ _outNotifyStmt(StringInfo str, NotifyStmt *node)
 	WRITE_NODE_FIELD(relation);
 }
 
+static void
+_outDeclareCursorStmt(StringInfo str, DeclareCursorStmt *node)
+{
+	WRITE_NODE_TYPE("DECLARECURSOR");
+
+	WRITE_STRING_FIELD(portalname);
+	WRITE_INT_FIELD(options);
+	WRITE_NODE_FIELD(query);
+}
+
 static void
 _outSelectStmt(StringInfo str, SelectStmt *node)
 {
@@ -1173,6 +1183,7 @@ _outQuery(StringInfo str, Query *node)
 			case T_CreateStmt:
 			case T_IndexStmt:
 			case T_NotifyStmt:
+			case T_DeclareCursorStmt:
 				WRITE_NODE_FIELD(utilityStmt);
 				break;
 			default:
@@ -1185,8 +1196,6 @@ _outQuery(StringInfo str, Query *node)
 
 	WRITE_INT_FIELD(resultRelation);
 	WRITE_NODE_FIELD(into);
-	WRITE_BOOL_FIELD(isPortal);
-	WRITE_BOOL_FIELD(isBinary);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasSubLinks);
 	WRITE_NODE_FIELD(rtable);
@@ -1684,6 +1693,9 @@ _outNode(StringInfo str, void *obj)
 			case T_NotifyStmt:
 				_outNotifyStmt(str, obj);
 				break;
+			case T_DeclareCursorStmt:
+				_outDeclareCursorStmt(str, obj);
+				break;
 			case T_SelectStmt:
 				_outSelectStmt(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 410d092c916404f0ab59cb232de9c31c8035898b..fb1ea0c94d095c8fe7e3c0ceada5d2d43dbb73eb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.149 2003/02/16 02:30:37 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.150 2003/03/10 03:53:49 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -198,8 +198,6 @@ _readQuery(void)
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
 	READ_NODE_FIELD(into);
-	READ_BOOL_FIELD(isPortal);
-	READ_BOOL_FIELD(isBinary);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasSubLinks);
 	READ_NODE_FIELD(rtable);
@@ -233,6 +231,21 @@ _readNotifyStmt(void)
 	READ_DONE();
 }
 
+/*
+ * _readDeclareCursorStmt
+ */
+static DeclareCursorStmt *
+_readDeclareCursorStmt(void)
+{
+	READ_LOCALS(DeclareCursorStmt);
+
+	READ_STRING_FIELD(portalname);
+	READ_INT_FIELD(options);
+	READ_NODE_FIELD(query);
+
+	READ_DONE();
+}
+
 /*
  * _readSortClause
  */
@@ -894,8 +907,6 @@ parseNodeString(void)
 
 	if (MATCH("QUERY", 5))
 		return_value = _readQuery();
-	else if (MATCH("NOTIFY", 6))
-		return_value = _readNotifyStmt();
 	else if (MATCH("SORTCLAUSE", 10))
 		return_value = _readSortClause();
 	else if (MATCH("GROUPCLAUSE", 11))
@@ -966,6 +977,10 @@ parseNodeString(void)
 		return_value = _readExprFieldSelect();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
+	else if (MATCH("NOTIFY", 6))
+		return_value = _readNotifyStmt();
+	else if (MATCH("DECLARECURSOR", 13))
+		return_value = _readDeclareCursorStmt();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 99d979d57c07dd5fae16353531a5e0a877f5f1cd..0bf43cab24df73713e6cacc53221503d7184cea0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.98 2003/03/05 20:01:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.99 2003/03/10 03:53:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -343,8 +343,7 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel,
 	}
 
 	/* Generate the plan for the subquery */
-	rel->subplan = subquery_planner(subquery,
-									-1.0 /* default case */ );
+	rel->subplan = subquery_planner(subquery, 0.0 /* default case */ );
 
 	/* Copy number of output rows from subplan */
 	rel->tuples = rel->subplan->plan_rows;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index bfc11aa004662cd6de53f7fc69e2112c7e809b50..d01acdc6182afb172913718bc904bcc2cea1378c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.137 2003/02/16 06:06:32 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.138 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1827,6 +1827,41 @@ make_material(List *tlist, Plan *lefttree)
 	return node;
 }
 
+/*
+ * materialize_finished_plan: stick a Material node atop a completed plan
+ *
+ * There are a couple of places where we want to attach a Material node
+ * after completion of subquery_planner().  This currently requires hackery.
+ * Since subquery_planner has already run SS_finalize_plan on the subplan
+ * tree, we have to kluge up parameter lists for the Material node.
+ * Possibly this could be fixed by postponing SS_finalize_plan processing
+ * until setrefs.c is run?
+ */
+Plan *
+materialize_finished_plan(Plan *subplan)
+{
+	Plan	   *matplan;
+	Path		matpath;		/* dummy for result of cost_material */
+
+	matplan = (Plan *) make_material(subplan->targetlist, subplan);
+
+	/* Set cost data */
+	cost_material(&matpath,
+				  subplan->total_cost,
+				  subplan->plan_rows,
+				  subplan->plan_width);
+	matplan->startup_cost = matpath.startup_cost;
+	matplan->total_cost = matpath.total_cost;
+	matplan->plan_rows = subplan->plan_rows;
+	matplan->plan_width = subplan->plan_width;
+
+	/* parameter kluge --- see comments above */
+	matplan->extParam = bms_copy(subplan->extParam);
+	matplan->allParam = bms_copy(subplan->allParam);
+
+	return matplan;
+}
+
 Agg *
 make_agg(Query *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy,
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index daa840f789e197f65a16da5208481dc23c6f6402..97f6b76a8e48c61dd12414cfb1bfafa5fce605c8 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.74 2003/01/20 18:54:52 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.75 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,14 +61,11 @@
  * indxpath.c need to see it.)
  *
  * tuple_fraction is interpreted as follows:
- *	  0 (or less): expect all tuples to be retrieved (normal case)
+ *	  0: expect all tuples to be retrieved (normal case)
  *	  0 < tuple_fraction < 1: expect the given fraction of tuples available
  *		from the plan to be retrieved
  *	  tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
  *		expected to be retrieved (ie, a LIMIT specification)
- * Note that while this routine and its subroutines treat a negative
- * tuple_fraction the same as 0, grouping_planner has a different
- * interpretation.
  *--------------------
  */
 void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c7c072fe2eba3e02a979726469a2229154c51f3b..9fa78b8a237acad26dcd4510c16d764746f6edcf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.150 2003/03/05 20:01:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.151 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
+#include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #ifdef OPTIMIZER_DEBUG
@@ -73,8 +74,9 @@ static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
  *
  *****************************************************************************/
 Plan *
-planner(Query *parse)
+planner(Query *parse, bool isCursor, int cursorOptions)
 {
+	double		tuple_fraction;
 	Plan	   *result_plan;
 	Index		save_PlannerQueryLevel;
 	List	   *save_PlannerParamVar;
@@ -99,11 +101,38 @@ planner(Query *parse)
 	PlannerQueryLevel = 0;		/* will be 1 in top-level subquery_planner */
 	PlannerParamVar = NIL;
 
+	/* Determine what fraction of the plan is likely to be scanned */
+	if (isCursor)
+	{
+		/*
+		 * We have no real idea how many tuples the user will ultimately
+		 * FETCH from a cursor, but it seems a good bet that he
+		 * doesn't want 'em all.  Optimize for 10% retrieval (you
+		 * gotta better number?  Should this be a SETtable parameter?)
+		 */
+		tuple_fraction = 0.10;
+	}
+	else
+	{
+		/* Default assumption is we need all the tuples */
+		tuple_fraction = 0.0;
+	}
+
 	/* primary planning entry point (may recurse for subqueries) */
-	result_plan = subquery_planner(parse, -1.0 /* default case */ );
+	result_plan = subquery_planner(parse, tuple_fraction);
 
 	Assert(PlannerQueryLevel == 0);
 
+	/*
+	 * If creating a plan for a scrollable cursor, make sure it can
+	 * run backwards on demand.  Add a Material node at the top at need.
+	 */
+	if (isCursor && (cursorOptions & CURSOR_OPT_SCROLL))
+	{
+		if (!ExecSupportsBackwardScan(result_plan))
+			result_plan = materialize_finished_plan(result_plan);
+	}
+
 	/* executor wants to know total number of Params used overall */
 	result_plan->nParamExec = length(PlannerParamVar);
 
@@ -505,14 +534,11 @@ inheritance_planner(Query *parse, List *inheritlist)
  * tuple_fraction is the fraction of tuples we expect will be retrieved
  *
  * tuple_fraction is interpreted as follows:
- *	  < 0: determine fraction by inspection of query (normal case)
- *	  0: expect all tuples to be retrieved
+ *	  0: expect all tuples to be retrieved (normal case)
  *	  0 < tuple_fraction < 1: expect the given fraction of tuples available
  *		from the plan to be retrieved
  *	  tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
  *		expected to be retrieved (ie, a LIMIT specification)
- * The normal case is to pass -1, but some callers pass values >= 0 to
- * override this routine's determination of the appropriate fraction.
  *
  * Returns a query plan.  Also, parse->query_pathkeys is returned as the
  * actual output ordering of the plan (in pathkey format).
@@ -693,29 +719,6 @@ grouping_planner(Query *parse, double tuple_fraction)
 		else
 			parse->query_pathkeys = NIL;
 
-		/*
-		 * Figure out whether we expect to retrieve all the tuples that
-		 * the plan can generate, or to stop early due to outside factors
-		 * such as a cursor.  If the caller passed a value >= 0, believe
-		 * that value, else do our own examination of the query context.
-		 */
-		if (tuple_fraction < 0.0)
-		{
-			/* Initial assumption is we need all the tuples */
-			tuple_fraction = 0.0;
-
-			/*
-			 * Check for retrieve-into-portal, ie DECLARE CURSOR.
-			 *
-			 * We have no real idea how many tuples the user will ultimately
-			 * FETCH from a cursor, but it seems a good bet that he
-			 * doesn't want 'em all.  Optimize for 10% retrieval (you
-			 * gotta better number?  Should this be a SETtable parameter?)
-			 */
-			if (parse->isPortal)
-				tuple_fraction = 0.10;
-		}
-
 		/*
 		 * Adjust tuple_fraction if we see that we are going to apply
 		 * limiting/grouping/aggregation/etc.  This is not overridable by
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fc428977c3318281cecb27f357929f3bcdf52a31..417eecc1fe3fa8d87821e17bc73a0788e68a942d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.72 2003/02/09 06:56:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.73 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -222,7 +222,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
 			 slink->subLinkType == ANY_SUBLINK)
 		tuple_fraction = 0.5;	/* 50% */
 	else
-		tuple_fraction = -1.0;	/* default behavior */
+		tuple_fraction = 0.0;	/* default behavior */
 
 	/*
 	 * Generate the plan for the subquery.
@@ -336,12 +336,6 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
 		 * is anything more complicated than a plain sequential scan, and we
 		 * do it even for seqscan if the qual appears selective enough to
 		 * eliminate many tuples.
-		 *
-		 * XXX It's pretty ugly to be inserting a MATERIAL node at this
-		 * point.  Since subquery_planner has already run SS_finalize_plan
-		 * on the subplan tree, we have to kluge up parameter lists for
-		 * the MATERIAL node.  Possibly this could be fixed by postponing
-		 * SS_finalize_plan processing until setrefs.c is run.
 		 */
 		else if (node->parParam == NIL)
 		{
@@ -380,23 +374,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
 			}
 			if (use_material)
 			{
-				Plan	   *matplan;
-				Path		matpath; /* dummy for result of cost_material */
-
-				matplan = (Plan *) make_material(plan->targetlist, plan);
-				/* need to calculate costs */
-				cost_material(&matpath,
-							  plan->total_cost,
-							  plan->plan_rows,
-							  plan->plan_width);
-				matplan->startup_cost = matpath.startup_cost;
-				matplan->total_cost = matpath.total_cost;
-				matplan->plan_rows = plan->plan_rows;
-				matplan->plan_width = plan->plan_width;
-				/* parameter kluge --- see comments above */
-				matplan->extParam = bms_copy(plan->extParam);
-				matplan->allParam = bms_copy(plan->allParam);
-				node->plan = plan = matplan;
+				node->plan = plan = materialize_finished_plan(plan);
 			}
 		}
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 207a813e8e019aefe1810260562019fe88333212..a265623bfb705284e1bf6d0cb202d57458df50a9 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -16,7 +16,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.6 2003/02/10 17:08:50 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.7 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -362,8 +362,7 @@ is_simple_subquery(Query *subquery)
 	if (!IsA(subquery, Query) ||
 		subquery->commandType != CMD_SELECT ||
 		subquery->resultRelation != 0 ||
-		subquery->into != NULL ||
-		subquery->isPortal)
+		subquery->into != NULL)
 		elog(ERROR, "is_simple_subquery: subquery is bogus");
 
 	/*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f0a64f2980c240924a1d82fa65460bd1fcea2354..1782bcc88b953a1c5e34258bb8f13302aeb62349 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.91 2003/03/05 20:01:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.92 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -134,8 +134,7 @@ recurse_set_operations(Node *setOp, Query *parse,
 		/*
 		 * Generate plan for primitive subquery
 		 */
-		subplan = subquery_planner(subquery,
-								   -1.0 /* default case */ );
+		subplan = subquery_planner(subquery, 0.0 /* default case */ );
 
 		/*
 		 * Add a SubqueryScan with the caller-requested targetlist
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 40e440a3754593fc9613dd1adbe475cfe53fbbd4..fcd583741893133c4b7b5cf31c15d749b6581761 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.130 2003/02/16 02:30:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.131 2003/03/10 03:53:50 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1787,7 +1787,6 @@ inline_function(Oid funcid, List *args, HeapTuple func_tuple,
 		querytree->commandType != CMD_SELECT ||
 		querytree->resultRelation != 0 ||
 		querytree->into ||
-		querytree->isPortal ||
 		querytree->hasAggs ||
 		querytree->hasSubLinks ||
 		querytree->rtable ||
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 2089008bbf55848ca25784f9491d8bef690291ba..3486a1e010b15358c7bb0cf7a40c4ce09b677699 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.264 2003/02/13 22:50:01 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.265 2003/03/10 03:53:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -96,6 +96,8 @@ static Query *transformSelectStmt(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 *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
 static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
 static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
@@ -313,6 +315,11 @@ transformStmt(ParseState *pstate, Node *parseTree,
 											   (SelectStmt *) parseTree);
 			break;
 
+		case T_DeclareCursorStmt:
+			result = transformDeclareCursorStmt(pstate,
+												(DeclareCursorStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -445,7 +452,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
 
 		Assert(IsA(selectQuery, Query));
 		Assert(selectQuery->commandType == CMD_SELECT);
-		if (selectQuery->into || selectQuery->isPortal)
+		if (selectQuery->into)
 			elog(ERROR, "INSERT ... SELECT may not specify INTO");
 
 		/*
@@ -1616,27 +1623,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	qry->commandType = CMD_SELECT;
 
-	if (stmt->portalname)
-	{
-		/* DECLARE CURSOR */
-		if (stmt->into)
-			elog(ERROR, "DECLARE CURSOR must not specify INTO");
-		if (stmt->forUpdate)
-			elog(ERROR, "DECLARE/UPDATE is not supported"
-				 "\n\tCursors must be READ ONLY");
-		qry->into = makeNode(RangeVar);
-		qry->into->relname = stmt->portalname;
-		qry->isPortal = TRUE;
-		qry->isBinary = stmt->binary;	/* internal portal */
-	}
-	else
-	{
-		/* SELECT */
-		qry->into = stmt->into;
-		qry->isPortal = FALSE;
-		qry->isBinary = FALSE;
-	}
-
 	/* make FOR UPDATE clause available to addRangeTableEntry */
 	pstate->p_forUpdate = stmt->forUpdate;
 
@@ -1646,6 +1632,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
+	/* handle any SELECT INTO/CREATE TABLE AS spec */
+	qry->into = stmt->into;
 	if (stmt->intoColNames)
 		applyColumnNames(qry->targetList, stmt->intoColNames);
 
@@ -1708,8 +1696,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	SetOperationStmt *sostmt;
 	RangeVar   *into;
 	List	   *intoColNames;
-	char	   *portalname;
-	bool		binary;
 	List	   *sortClause;
 	Node	   *limitOffset;
 	Node	   *limitCount;
@@ -1738,14 +1724,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 		   leftmostSelect->larg == NULL);
 	into = leftmostSelect->into;
 	intoColNames = leftmostSelect->intoColNames;
-	portalname = stmt->portalname;
-	binary = stmt->binary;
 
 	/* clear them to prevent complaints in transformSetOperationTree() */
 	leftmostSelect->into = NULL;
 	leftmostSelect->intoColNames = NIL;
-	stmt->portalname = NULL;
-	stmt->binary = false;
 
 	/*
 	 * These are not one-time, exactly, but we want to process them here
@@ -1825,36 +1807,13 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	}
 
 	/*
-	 * Insert one-time items into top-level query
+	 * Handle SELECT INTO/CREATE TABLE AS.
 	 *
-	 * This needs to agree with transformSelectStmt!
-	 */
-	if (portalname)
-	{
-		/* DECLARE CURSOR */
-		if (into)
-			elog(ERROR, "DECLARE CURSOR must not specify INTO");
-		if (forUpdate)
-			elog(ERROR, "DECLARE/UPDATE is not supported"
-				 "\n\tCursors must be READ ONLY");
-		qry->into = makeNode(RangeVar);
-		qry->into->relname = portalname;
-		qry->isPortal = TRUE;
-		qry->isBinary = binary; /* internal portal */
-	}
-	else
-	{
-		/* SELECT */
-		qry->into = into;
-		qry->isPortal = FALSE;
-		qry->isBinary = FALSE;
-	}
-
-	/*
 	 * Any column names from CREATE TABLE AS need to be attached to both
 	 * the top level and the leftmost subquery.  We do not do this earlier
 	 * because we do *not* want the targetnames list to be affected.
 	 */
+	qry->into = into;
 	if (intoColNames)
 	{
 		applyColumnNames(qry->targetList, intoColNames);
@@ -1938,8 +1897,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 	 */
 	if (stmt->into)
 		elog(ERROR, "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
-	if (stmt->portalname)		/* should not happen */
-		elog(ERROR, "Portal may not appear in UNION/INTERSECT/EXCEPT");
 	/* We don't support forUpdate with set ops at the moment. */
 	if (stmt->forUpdate)
 		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
@@ -2327,6 +2284,27 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
 	return qry;
 }
 
+static Query *
+transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
+{
+	Query	   *result = makeNode(Query);
+	List	   *extras_before = NIL,
+			   *extras_after = NIL;
+
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	stmt->query = (Node *) 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, "transformDeclareCursorStmt: internal error");
+
+	return result;
+}
+
+
 static Query *
 transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt)
 {
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c593196dfc869179e570ac07071fed0a71a0a573..045d3cc2ca24f2e20713526823852a91a89b4b35 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.404 2003/02/16 02:30:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.405 2003/03/10 03:53:50 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -135,12 +135,12 @@ static void doNegateFloat(Value *v);
 		CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
 		CreateSchemaStmt CreateSeqStmt CreateStmt
 		CreateAssertStmt CreateTrigStmt CreateUserStmt
-		CreatedbStmt CursorStmt DefineStmt DeleteStmt
+		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt
 		DropGroupStmt DropOpClassStmt DropPLangStmt DropStmt
 		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt
 		DropUserStmt DropdbStmt ExplainStmt FetchStmt
 		GrantStmt IndexStmt InsertStmt ListenStmt LoadStmt
-		LockStmt NotifyStmt OptimizableStmt
+		LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt ReindexStmt RemoveAggrStmt
 		RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -241,7 +241,7 @@ static void doNegateFloat(Value *v);
 %type <ival>	opt_interval
 %type <node>	overlay_placing substr_from substr_for
 
-%type <boolean> opt_instead opt_cursor opt_analyze
+%type <boolean> opt_instead opt_analyze
 %type <boolean> index_opt_unique opt_verbose opt_full
 %type <boolean> opt_freeze opt_default opt_recheck
 %type <defelt>	opt_binary opt_oids copy_delimiter
@@ -249,7 +249,7 @@ static void doNegateFloat(Value *v);
 %type <boolean> copy_from
 
 %type <ival>	direction reindex_type drop_type
-				opt_column event comment_type
+				opt_column event comment_type cursor_options
 
 %type <ival>	fetch_how_many
 
@@ -481,68 +481,72 @@ stmt :
 			| AlterDomainStmt
 			| AlterGroupStmt
 			| AlterTableStmt
-			| AlterUserStmt
 			| AlterUserSetStmt
+			| AlterUserStmt
+			| AnalyzeStmt
+			| CheckPointStmt
 			| ClosePortalStmt
+			| ClusterStmt
+			| CommentStmt
+			| ConstraintsSetStmt
 			| CopyStmt
-			| CreateStmt
 			| CreateAsStmt
+			| CreateAssertStmt
 			| CreateCastStmt
+			| CreateConversionStmt
 			| CreateDomainStmt
 			| CreateFunctionStmt
-			| CreateSchemaStmt
 			| CreateGroupStmt
-			| CreateSeqStmt
 			| CreateOpClassStmt
 			| CreatePLangStmt
-			| CreateAssertStmt
+			| CreateSchemaStmt
+			| CreateSeqStmt
+			| CreateStmt
 			| CreateTrigStmt
 			| CreateUserStmt
-			| ClusterStmt
+			| CreatedbStmt
 			| DeallocateStmt
+			| DeclareCursorStmt
 			| DefineStmt
-			| DropStmt
-			| TruncateStmt
-			| CommentStmt
+			| DeleteStmt
+			| DropAssertStmt
 			| DropCastStmt
 			| DropGroupStmt
 			| DropOpClassStmt
 			| DropPLangStmt
-			| DropAssertStmt
-			| DropTrigStmt
 			| DropRuleStmt
+			| DropStmt
+			| DropTrigStmt
 			| DropUserStmt
+			| DropdbStmt
 			| ExecuteStmt
 			| ExplainStmt
 			| FetchStmt
 			| GrantStmt
 			| IndexStmt
+			| InsertStmt
 			| ListenStmt
-			| UnlistenStmt
+			| LoadStmt
 			| LockStmt
 			| NotifyStmt
 			| PrepareStmt
 			| ReindexStmt
 			| RemoveAggrStmt
-			| RemoveOperStmt
 			| RemoveFuncStmt
+			| RemoveOperStmt
 			| RenameStmt
 			| RevokeStmt
-			| OptimizableStmt
 			| RuleStmt
+			| SelectStmt
 			| TransactionStmt
-			| ViewStmt
-			| LoadStmt
-			| CreatedbStmt
-			| DropdbStmt
+			| TruncateStmt
+			| UnlistenStmt
+			| UpdateStmt
 			| VacuumStmt
-			| AnalyzeStmt
+			| VariableResetStmt
 			| VariableSetStmt
 			| VariableShowStmt
-			| VariableResetStmt
-			| ConstraintsSetStmt
-			| CheckPointStmt
-			| CreateConversionStmt
+			| ViewStmt
 			| /*EMPTY*/
 				{ $$ = (Node *)NULL; }
 		;
@@ -3961,16 +3965,7 @@ opt_name_list:
  *
  *****************************************************************************/
 
-ExplainStmt:
-			EXPLAIN opt_analyze opt_verbose OptimizableStmt
-				{
-					ExplainStmt *n = makeNode(ExplainStmt);
-					n->analyze = $2;
-					n->verbose = $3;
-					n->query = (Query*)$4;
-					$$ = (Node *)n;
-				}
-			| EXPLAIN opt_analyze opt_verbose ExecuteStmt
+ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
 				{
 					ExplainStmt *n = makeNode(ExplainStmt);
 					n->analyze = $2;
@@ -3980,6 +3975,15 @@ ExplainStmt:
 				}
 		;
 
+ExplainableStmt:
+			SelectStmt
+			| InsertStmt
+			| UpdateStmt
+			| DeleteStmt
+			| DeclareCursorStmt
+			| ExecuteStmt					/* by default all are $$=$1 */
+		;
+
 opt_analyze:
 			analyze_keyword			{ $$ = TRUE; }
 			| /* EMPTY */			{ $$ = FALSE; }
@@ -3992,7 +3996,7 @@ opt_analyze:
  *
  *****************************************************************************/
 
-PrepareStmt: PREPARE name prep_type_clause AS OptimizableStmt
+PrepareStmt: PREPARE name prep_type_clause AS PreparableStmt
 				{
 					PrepareStmt *n = makeNode(PrepareStmt);
 					n->name = $2;
@@ -4011,6 +4015,13 @@ prep_type_list: Typename			{ $$ = makeList1($1); }
 									{ $$ = lappend($1, $3); }
 		;
 
+PreparableStmt:
+			SelectStmt
+			| InsertStmt
+			| UpdateStmt
+			| DeleteStmt					/* by default all are $$=$1 */
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -4053,26 +4064,6 @@ DeallocateStmt: DEALLOCATE name
 					}
 		;
 
-/*****************************************************************************
- *																			 *
- *		Optimizable Stmts:													 *
- *																			 *
- *		one of the five queries processed by the planner					 *
- *																			 *
- *		[ultimately] produces query-trees as specified						 *
- *		in the query-spec document in ~postgres/ref							 *
- *																			 *
- *****************************************************************************/
-
-OptimizableStmt:
-			SelectStmt
-			| CursorStmt
-			| UpdateStmt
-			| InsertStmt
-			| DeleteStmt					/* by default all are $$=$1 */
-		;
-
-
 /*****************************************************************************
  *
  *		QUERY:
@@ -4213,20 +4204,20 @@ UpdateStmt: UPDATE relation_expr
  *				CURSOR STATEMENTS
  *
  *****************************************************************************/
-CursorStmt: DECLARE name opt_cursor CURSOR FOR SelectStmt
+DeclareCursorStmt: DECLARE name cursor_options CURSOR FOR SelectStmt
 				{
-					SelectStmt *n = (SelectStmt *)$6;
+					DeclareCursorStmt *n = makeNode(DeclareCursorStmt);
 					n->portalname = $2;
-					n->binary = $3;
-					$$ = $6;
+					n->options = $3;
+					n->query = $6;
+					$$ = (Node *)n;
 				}
 		;
 
-opt_cursor: BINARY									{ $$ = TRUE; }
-			| INSENSITIVE							{ $$ = FALSE; }
-			| SCROLL								{ $$ = FALSE; }
-			| INSENSITIVE SCROLL					{ $$ = FALSE; }
-			| /*EMPTY*/								{ $$ = FALSE; }
+cursor_options: /*EMPTY*/					{ $$ = 0; }
+			| cursor_options BINARY			{ $$ = $1 | CURSOR_OPT_BINARY; }
+			| cursor_options SCROLL			{ $$ = $1 | CURSOR_OPT_SCROLL; }
+			| cursor_options INSENSITIVE	{ $$ = $1 | CURSOR_OPT_INSENSITIVE; }
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 33e7cce420390774765d89403e75935c6ed0222c..d4c13165e5a4ca158fe96459891c71bb57cda234 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.110 2003/02/16 02:30:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.111 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -414,7 +414,7 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 
 	if (query->commandType != CMD_SELECT)
 		elog(ERROR, "Expected SELECT query from subselect in FROM");
-	if (query->resultRelation != 0 || query->into != NULL || query->isPortal)
+	if (query->resultRelation != 0 || query->into != NULL)
 		elog(ERROR, "Subselect in FROM may not have SELECT INTO");
 
 	/*
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index e5f1e88ffdb7250633f8c575aff57525cf471972..1f0b7639d5ffc778616c6d8cdf7904489ec44e09 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.53 2003/02/19 23:41:15 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_type.c,v 1.54 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -455,7 +455,6 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod)
 		stmt->groupClause != NIL ||
 		stmt->havingClause != NULL ||
 		stmt->sortClause != NIL ||
-		stmt->portalname != NULL ||
 		stmt->limitOffset != NULL ||
 		stmt->limitCount != NULL ||
 		stmt->forUpdate != NIL ||
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b4f064d0e1b52c9e378bf36a6a5d866a79d3087f..ec717d8c122fbb0c5d6bd511de5e77864f4525c9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.316 2003/03/06 00:04:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.317 2003/03/10 03:53:51 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -483,7 +483,7 @@ pg_plan_query(Query *querytree)
 		ResetUsage();
 
 	/* call the optimizer */
-	plan = planner(querytree);
+	plan = planner(querytree, false, 0);
 
 	if (log_planner_stats)
 		ShowUsage("PLANNER STATISTICS");
@@ -1789,7 +1789,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.316 $ $Date: 2003/03/06 00:04:27 $\n");
+		puts("$Revision: 1.317 $ $Date: 2003/03/10 03:53:51 $\n");
 	}
 
 	/*
@@ -2245,6 +2245,10 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_DeclareCursorStmt:
+			tag = "DECLARE CURSOR";
+			break;
+
 		case T_ClosePortalStmt:
 			tag = "CLOSE CURSOR";
 			break;
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 1e02e42193df31e0a30307b0b173b0fa1153ef85..29d5018440dee68ec5f58644aefa0c75f321de73 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,19 +8,15 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.58 2002/12/15 16:17:52 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.59 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
-#include "commands/portalcmds.h"
-#include "executor/execdefs.h"
 #include "executor/executor.h"
 #include "tcop/pquery.h"
-#include "utils/memutils.h"
-#include "utils/ps_status.h"
 
 
 /*
@@ -64,38 +60,6 @@ FreeQueryDesc(QueryDesc *qdesc)
 	pfree(qdesc);
 }
 
-/* ----------------
- *		PreparePortal
- * ----------------
- */
-Portal
-PreparePortal(char *portalName)
-{
-	Portal		portal;
-
-	/*
-	 * Check for already-in-use portal name.
-	 */
-	portal = GetPortalByName(portalName);
-	if (PortalIsValid(portal))
-	{
-		/*
-		 * XXX Should we raise an error rather than closing the old
-		 * portal?
-		 */
-		elog(WARNING, "Closing pre-existing portal \"%s\"",
-			 portalName);
-		PortalDrop(portal);
-	}
-
-	/*
-	 * Create the new portal.
-	 */
-	portal = CreatePortal(portalName);
-
-	return portal;
-}
-
 
 /*
  * ProcessQuery
@@ -116,10 +80,6 @@ ProcessQuery(Query *parsetree,
 			 char *completionTag)
 {
 	int			operation = parsetree->commandType;
-	bool		isRetrieveIntoPortal = false;
-	char	   *intoName = NULL;
-	Portal		portal = NULL;
-	MemoryContext oldContext = NULL;
 	QueryDesc  *queryDesc;
 
 	/*
@@ -127,16 +87,7 @@ ProcessQuery(Query *parsetree,
 	 */
 	if (operation == CMD_SELECT)
 	{
-		if (parsetree->isPortal)
-		{
-			isRetrieveIntoPortal = true;
-			/* If binary portal, switch to alternate output format */
-			if (dest == Remote && parsetree->isBinary)
-				dest = RemoteInternal;
-			/* Check for invalid context (must be in transaction block) */
-			RequireTransactionChain((void *) parsetree, "DECLARE CURSOR");
-		}
-		else if (parsetree->into != NULL)
+		if (parsetree->into != NULL)
 		{
 			/*
 			 * SELECT INTO table (a/k/a CREATE AS ... SELECT).
@@ -150,57 +101,17 @@ ProcessQuery(Query *parsetree,
 	}
 
 	/*
-	 * If retrieving into a portal, set up the portal and copy the
-	 * parsetree and plan into its memory context.
+	 * Create the QueryDesc object
 	 */
-	if (isRetrieveIntoPortal)
-	{
-		intoName = parsetree->into->relname;
-		portal = PreparePortal(intoName);
-		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-		parsetree = copyObject(parsetree);
-		plan = copyObject(plan);
-		intoName = parsetree->into->relname;	/* use copied name in
-												 * QueryDesc */
-
-		/*
-		 * We stay in portal's memory context for now, so that query desc
-		 * is also allocated in the portal context.
-		 */
-	}
+	queryDesc = CreateQueryDesc(parsetree, plan, dest, NULL, NULL, false);
 
 	/*
-	 * Now we can create the QueryDesc object.
-	 */
-	queryDesc = CreateQueryDesc(parsetree, plan, dest, intoName, NULL, false);
-
-	/*
-	 * call ExecStart to prepare the plan for execution
+	 * Call ExecStart to prepare the plan for execution
 	 */
 	ExecutorStart(queryDesc);
 
 	/*
-	 * If retrieve into portal, stop now; we do not run the plan until a
-	 * FETCH command is received.
-	 */
-	if (isRetrieveIntoPortal)
-	{
-		/* Arrange to shut down the executor if portal is dropped */
-		PortalSetQuery(portal, queryDesc, PortalCleanup);
-
-		/* Now we can return to caller's memory context. */
-		MemoryContextSwitchTo(oldContext);
-
-		/* Set completion tag.	SQL calls this operation DECLARE CURSOR */
-		if (completionTag)
-			strcpy(completionTag, "DECLARE CURSOR");
-
-		return;
-	}
-
-	/*
-	 * Now we get to the important call to ExecutorRun() where we actually
-	 * run the plan..
+	 * And run the plan.
 	 */
 	ExecutorRun(queryDesc, ForwardScanDirection, 0L);
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index db7dc0945cd0c74e23cd3b3b3553f1e84577b81d..0fae711a2c320930aaae5fef4583e78929e48a06 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.193 2003/02/19 03:59:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.194 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -306,13 +306,17 @@ ProcessUtility(Node *parsetree,
 			break;
 
 			/*
-			 * ************************* portal manipulation ***************************
+			 * Portal (cursor) manipulation
 			 */
+		case T_DeclareCursorStmt:
+			PerformCursorOpen((DeclareCursorStmt *) parsetree, dest);
+			break;
+
 		case T_ClosePortalStmt:
 			{
 				ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
 
-				PerformPortalClose(stmt->portalname, dest);
+				PerformPortalClose(stmt->portalname);
 			}
 			break;
 
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 520a55d64ae504cd45b975f1ed7df77ff0677b28..654247dd8c5a848f7157079065714ade58c1aa56 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.51 2002/12/30 22:10:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.52 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,16 +33,25 @@
 
 #include "postgres.h"
 
+#include "executor/executor.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 
+
+/*
+ * estimate of the maximum number of open portals a user would have,
+ * used in initially sizing the PortalHashTable in EnablePortalManager()
+ */
+#define PORTALS_PER_USER	   64
+
+
 /* ----------------
  *		Global state
  * ----------------
  */
 
-#define MAX_PORTALNAME_LEN		64
+#define MAX_PORTALNAME_LEN		NAMEDATALEN
 
 typedef struct portalhashent
 {
@@ -158,7 +167,8 @@ PortalSetQuery(Portal portal,
 	AssertArg(PortalIsValid(portal));
 
 	portal->queryDesc = queryDesc;
-	portal->atStart = true;		/* Allow fetch forward only */
+	portal->backwardOK = ExecSupportsBackwardScan(queryDesc->plantree);
+	portal->atStart = true;		/* Allow fetch forward only, to start */
 	portal->atEnd = false;
 	portal->cleanup = cleanup;
 }
@@ -201,6 +211,7 @@ CreatePortal(const char *name)
 
 	/* initialize portal query */
 	portal->queryDesc = NULL;
+	portal->backwardOK = false;
 	portal->atStart = true;		/* disallow fetches until query is set */
 	portal->atEnd = true;
 	portal->cleanup = NULL;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index fc24db5d2e1a455bb9c711067aa1fe942c4561ef..6344f0bae535cfb338c93edc7ae7e77bb26ac72d 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.179 2003/02/22 00:45:05 tgl Exp $
+ * $Id: catversion.h,v 1.180 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200302211
+#define CATALOG_VERSION_NO	200303091
 
 #endif
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index d143423f6a32586d71879e142fab37b821e5ab75..3f2a4221add76007db73e7b07fc2ea6276649338 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: portalcmds.h,v 1.4 2002/12/30 15:31:50 momjian Exp $
+ * $Id: portalcmds.h,v 1.5 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,23 +16,16 @@
 
 #include "utils/portal.h"
 
-/*
- * PerformPortalFetch
- *		Performs the POSTQUEL function FETCH.  Fetches count
- * tuples in portal with name in the forward direction iff goForward.
- *
- * Exceptions:
- *		BadArg if forward invalid.
- *		"ERROR" if portal not found.
- */
+
+extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
+
 extern void PerformPortalFetch(char *name, bool forward, long count,
 				   CommandDest dest, char *completionTag);
 
-/*
- * PerformPortalClose
- *		Performs the POSTQUEL function CLOSE.
- */
-extern void PerformPortalClose(char *name, CommandDest dest);
+extern long DoPortalFetch(Portal portal, bool forward, long count,
+						  CommandDest dest);
+
+extern void PerformPortalClose(char *name);
 
 extern void PortalCleanup(Portal portal);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ee30f51896974e74b279589bedf93f6437d9bb0f..785d21718b2b5328e8ddaaf39a6efecef73aab60 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.89 2003/02/09 00:30:39 tgl Exp $
+ * $Id: executor.h,v 1.90 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -35,6 +35,7 @@ extern void ExecReScan(PlanState *node, ExprContext *exprCtxt);
 extern void ExecMarkPos(PlanState *node);
 extern void ExecRestrPos(PlanState *node);
 extern bool ExecSupportsMarkRestore(NodeTag plantype);
+extern bool ExecSupportsBackwardScan(Plan *node);
 
 /*
  * prototypes from functions in execGrouping.c
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 85a987b14b23bccfdd53820849af37ad0fd6647a..ead328da24758c18d916deef3bc24a68cdf75cc3 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -2,7 +2,7 @@
  *
  * spi.h
  *
- * $Id: spi.h,v 1.35 2002/12/30 22:10:54 tgl Exp $
+ * $Id: spi.h,v 1.36 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,9 +30,10 @@
 #include "tcop/utility.h"
 #include "tcop/dest.h"
 #include "nodes/params.h"
+#include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/portal.h"
 #include "utils/syscache.h"
-#include "utils/builtins.h"
 #include "catalog/pg_language.h"
 #include "access/heapam.h"
 #include "access/xact.h"
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 356f4b60fa3726a7701a7bc669c3df35336aea47..3304ea89e4e5a1912c76338086c04b524ea3613e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.137 2003/02/16 02:30:39 tgl Exp $
+ * $Id: nodes.h,v 1.138 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -248,6 +248,7 @@ typedef enum NodeTag
 	T_PrepareStmt,
 	T_ExecuteStmt,
 	T_DeallocateStmt,
+	T_DeclareCursorStmt,
 
 	T_A_Expr = 800,
 	T_ColumnRef,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 381c11c3893b72e0fa710150fdce4dccc5700f6a..c84348ded9e85383ed7ce95690866550420dc8b4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.231 2003/02/16 02:30:39 tgl Exp $
+ * $Id: parsenodes.h,v 1.232 2003/03/10 03:53:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,9 +38,6 @@ typedef enum QuerySource
  *	  for further processing by the optimizer
  *	  utility statements (i.e. non-optimizable statements)
  *	  have the *utilityStmt field set.
- *
- * we need the isPortal flag because portal names can be null too; can
- * get rid of it if we support CURSOR as a commandType.
  */
 typedef struct Query
 {
@@ -54,10 +51,8 @@ typedef struct Query
 								 * statement */
 
 	int			resultRelation; /* target relation (index into rtable) */
-	RangeVar   *into;			/* target relation or portal (cursor) for
-								 * portal just name is meaningful */
-	bool		isPortal;		/* is this a retrieve into portal? */
-	bool		isBinary;		/* binary portal? */
+
+	RangeVar   *into;			/* target relation for SELECT INTO */
 
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -597,6 +592,8 @@ typedef struct SelectStmt
 
 	/*
 	 * These fields are used only in "leaf" SelectStmts.
+	 *
+	 * into and intoColNames are a kluge; they belong somewhere else...
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT
@@ -611,11 +608,9 @@ typedef struct SelectStmt
 
 	/*
 	 * These fields are used in both "leaf" SelectStmts and upper-level
-	 * SelectStmts.  portalname/binary may only be set at the top level.
+	 * SelectStmts.
 	 */
 	List	   *sortClause;		/* sort clause (a list of SortGroupBy's) */
-	char	   *portalname;		/* the portal (cursor) to create */
-	bool		binary;			/* a binary (internal) portal? */
 	Node	   *limitOffset;	/* # of result tuples to skip */
 	Node	   *limitCount;		/* # of result tuples to return */
 	List	   *forUpdate;		/* FOR UPDATE clause */
@@ -815,16 +810,6 @@ typedef struct PrivTarget
 	List	   *objs;
 } PrivTarget;
 
-/* ----------------------
- *		Close Portal Statement
- * ----------------------
- */
-typedef struct ClosePortalStmt
-{
-	NodeTag		type;
-	char	   *portalname;		/* name of the portal (cursor) */
-} ClosePortalStmt;
-
 /* ----------------------
  *		Copy Statement
  * ----------------------
@@ -1212,7 +1197,33 @@ typedef struct CommentStmt
 } CommentStmt;
 
 /* ----------------------
- *		Fetch Statement
+ *		Declare Cursor Statement
+ * ----------------------
+ */
+#define CURSOR_OPT_BINARY		0x0001
+#define CURSOR_OPT_SCROLL		0x0002
+#define CURSOR_OPT_INSENSITIVE	0x0004
+
+typedef struct DeclareCursorStmt
+{
+	NodeTag		type;
+	char	   *portalname;		/* name of the portal (cursor) */
+	int			options;		/* bitmask of options (see above) */
+	Node	   *query;			/* the SELECT query */
+} DeclareCursorStmt;
+
+/* ----------------------
+ *		Close Portal Statement
+ * ----------------------
+ */
+typedef struct ClosePortalStmt
+{
+	NodeTag		type;
+	char	   *portalname;		/* name of the portal (cursor) */
+} ClosePortalStmt;
+
+/* ----------------------
+ *		Fetch Statement (also Move)
  * ----------------------
  */
 typedef enum FetchDirection
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 399b3bb1310eaafd512c1f33a21cedae04c9dacd..bd1d757e6a7d90f8e061832cc41c76c99eed380d 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.68 2003/01/24 03:58:44 tgl Exp $
+ * $Id: planmain.h,v 1.69 2003/03/10 03:53:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,6 +44,7 @@ extern Group *make_group(Query *root, List *tlist,
 						 double numGroups,
 						 Plan *lefttree);
 extern Material *make_material(List *tlist, Plan *lefttree);
+extern Plan *materialize_finished_plan(Plan *subplan);
 extern Unique *make_unique(List *tlist, Plan *lefttree, List *distinctList);
 extern Limit *make_limit(List *tlist, Plan *lefttree,
 		   Node *limitOffset, Node *limitCount);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 16885b2f1383ebf29d83123b8e809ee076f47f7a..f9500202ce85e2969bf690873ffb64c83d46a25b 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planner.h,v 1.25 2003/01/20 18:55:05 tgl Exp $
+ * $Id: planner.h,v 1.26 2003/03/10 03:53:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,7 @@
 #include "nodes/plannodes.h"
 
 
-extern Plan *planner(Query *parse);
+extern Plan *planner(Query *parse, bool isCursor, int cursorOptions);
 extern Plan *subquery_planner(Query *parse, double tuple_fraction);
 
 #endif   /* PLANNER_H */
diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h
index b00142499777b306b50b5802e74cc038ce8273e3..c992306a9af377c9b5eb79edb04724c9ff87b3d1 100644
--- a/src/include/tcop/pquery.h
+++ b/src/include/tcop/pquery.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pquery.h,v 1.23 2002/12/05 15:50:39 tgl Exp $
+ * $Id: pquery.h,v 1.24 2003/03/10 03:53:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,12 +15,9 @@
 #define PQUERY_H
 
 #include "executor/execdesc.h"
-#include "utils/portal.h"
 
 
 extern void ProcessQuery(Query *parsetree, Plan *plan, CommandDest dest,
 			 char *completionTag);
 
-extern Portal PreparePortal(char *portalName);
-
 #endif   /* PQUERY_H */
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 41fa2f735c2b15b19504ecb3621658a5f2c50487..21469dd52df442631cb6632f10e96a25acaf09af 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: portal.h,v 1.37 2002/12/30 22:10:54 tgl Exp $
+ * $Id: portal.h,v 1.38 2003/03/10 03:53:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,8 +27,9 @@ typedef struct PortalData
 	char	   *name;			/* Portal's name */
 	MemoryContext heap;			/* subsidiary memory */
 	QueryDesc  *queryDesc;		/* Info about query associated with portal */
-	bool		atStart;		/* T => fetch backwards is not allowed */
-	bool		atEnd;			/* T => fetch forwards is not allowed */
+	bool		backwardOK;		/* is fetch backwards allowed at all? */
+	bool		atStart;		/* T => fetch backwards is not allowed now */
+	bool		atEnd;			/* T => fetch forwards is not allowed now */
 	void		(*cleanup) (Portal);	/* Cleanup routine (optional) */
 } PortalData;
 
@@ -44,12 +45,6 @@ typedef struct PortalData
 #define PortalGetQueryDesc(portal)	((portal)->queryDesc)
 #define PortalGetHeapMemory(portal) ((portal)->heap)
 
-/*
- * estimate of the maximum number of open portals a user would have,
- * used in initially sizing the PortalHashTable in EnablePortalManager()
- */
-#define PORTALS_PER_USER	   64
-
 
 extern void EnablePortalManager(void);
 extern void AtEOXact_portals(void);