From 66888f7424f7d6c7cea2c26e181054d1455d4e7a Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 16 Apr 2007 01:14:58 +0000
Subject: [PATCH] Expose more cursor-related functionality in SPI:
 specifically, allow access to the planner's cursor-related planning options,
 and provide new FETCH/MOVE routines that allow access to the full power of
 those commands. Small refactoring of planner(), pg_plan_query(), and
 pg_plan_queries() APIs to make it convenient to pass the planning options
 down from SPI.

This is the core-code portion of Pavel Stehule's patch for scrollable
cursor support in plpgsql; I'll review and apply the plpgsql changes
separately.
---
 doc/src/sgml/spi.sgml                | 322 ++++++++++++++++++++++++++-
 src/backend/commands/copy.c          |   4 +-
 src/backend/commands/explain.c       |  12 +-
 src/backend/commands/portalcmds.c    |   4 +-
 src/backend/commands/prepare.c       |   4 +-
 src/backend/executor/functions.c     |   4 +-
 src/backend/executor/spi.c           |  63 +++++-
 src/backend/optimizer/plan/planner.c |   9 +-
 src/backend/parser/gram.y            |  17 +-
 src/backend/tcop/postgres.c          |  21 +-
 src/backend/utils/cache/plancache.c  |   9 +-
 src/include/executor/spi.h           |  11 +-
 src/include/nodes/parsenodes.h       |  13 +-
 src/include/optimizer/planner.h      |   4 +-
 src/include/tcop/tcopprot.h          |   9 +-
 15 files changed, 435 insertions(+), 71 deletions(-)

diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 14e975b5fc..7ee47162cd 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.55 2007/03/25 23:27:59 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.56 2007/04/16 01:14:55 tgl Exp $ -->
 
 <chapter id="spi">
  <title>Server Programming Interface</title>
@@ -799,6 +799,104 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
 
 <!-- *********************************************** -->
 
+<refentry id="spi-spi-prepare-cursor">
+ <refmeta>
+  <refentrytitle>SPI_prepare_cursor</refentrytitle>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_prepare_cursor</refname>
+  <refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
+ </refnamediv>
+
+ <indexterm><primary>SPI_prepare_cursor</primary></indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <parameter>nargs</parameter>, Oid * <parameter>argtypes</parameter>, int <parameter>cursorOptions</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_prepare_cursor</function> is identical to
+   <function>SPI_prepare</function>, except that it also allows specification
+   of the planner's <quote>cursor options</> parameter.  This is a bitmask
+   having the values shown in <filename>nodes/parsenodes.h</filename>
+   for the <structfield>options</> field of <structname>DeclareCursorStmt</>.
+   <function>SPI_prepare</function> always takes these options as zero.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>const char * <parameter>command</parameter></literal></term>
+    <listitem>
+     <para>
+      command string
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>int <parameter>nargs</parameter></literal></term>
+    <listitem>
+     <para>
+      number of input parameters (<literal>$1</>, <literal>$2</>, etc.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>Oid * <parameter>argtypes</parameter></literal></term>
+    <listitem>
+     <para>
+      pointer to an array containing the <acronym>OID</acronym>s of
+      the data types of the parameters
+     </para>
+    </listitem>
+   </varlistentry>
+ 
+   <varlistentry>
+    <term><literal>int <parameter>cursorOptions</parameter></literal></term>
+    <listitem>
+     <para>
+      integer bitmask of cursor options; zero produces default behavior
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   <function>SPI_prepare_cursor</function> has the same return conventions as
+   <function>SPI_prepare</function>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Useful bits to set in <parameter>cursorOptions</> include
+   <symbol>CURSOR_OPT_SCROLL</symbol>,
+   <symbol>CURSOR_OPT_NO_SCROLL</symbol>, and
+   <symbol>CURSOR_OPT_FAST_PLAN</symbol>.  Note in particular that
+   <symbol>CURSOR_OPT_HOLD</symbol> is ignored.
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
 <refentry id="spi-spi-getargcount">
  <refmeta>
   <refentrytitle>SPI_getargcount</refentrytitle>
@@ -1430,7 +1528,9 @@ void SPI_cursor_fetch(Portal <parameter>portal</parameter>, bool <parameter>forw
 
   <para>
    <function>SPI_cursor_fetch</function> fetches some rows from a
-   cursor.  This is equivalent to the SQL command <command>FETCH</>.
+   cursor.  This is equivalent to a subset of the SQL command
+   <command>FETCH</> (see <function>SPI_scroll_cursor_fetch</function>
+   for more functionality).
   </para>
  </refsect1>
 
@@ -1476,6 +1576,15 @@ void SPI_cursor_fetch(Portal <parameter>portal</parameter>, bool <parameter>forw
    <function>SPI_execute</function> if successful.
   </para>
  </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Fetching backward may fail if the cursor's plan was not created
+   with the <symbol>CURSOR_OPT_SCROLL</symbol> option.
+  </para>
+ </refsect1>
 </refentry>
 
 <!-- *********************************************** -->
@@ -1503,8 +1612,9 @@ void SPI_cursor_move(Portal <parameter>portal</parameter>, bool <parameter>forwa
 
   <para>
    <function>SPI_cursor_move</function> skips over some number of rows
-   in a cursor.  This is equivalent to the SQL command
-   <command>MOVE</>.
+   in a cursor.  This is equivalent to a subset of the SQL command
+   <command>MOVE</> (see <function>SPI_scroll_cursor_move</function>
+   for more functionality).
   </para>
  </refsect1>
 
@@ -1540,6 +1650,210 @@ void SPI_cursor_move(Portal <parameter>portal</parameter>, bool <parameter>forwa
    </varlistentry>
   </variablelist>
  </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   Moving backward may fail if the cursor's plan was not created
+   with the <symbol>CURSOR_OPT_SCROLL</symbol> option.
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
+<refentry id="spi-spi-scroll-cursor-fetch">
+ <refmeta>
+  <refentrytitle>SPI_scroll_cursor_fetch</refentrytitle>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_scroll_cursor_fetch</refname>
+  <refpurpose>fetch some rows from a cursor</refpurpose>
+ </refnamediv>
+
+ <indexterm><primary>SPI_scroll_cursor_fetch</primary></indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+void SPI_scroll_cursor_fetch(Portal <parameter>portal</parameter>, FetchDirection <parameter>direction</parameter>, long <parameter>count</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_scroll_cursor_fetch</function> fetches some rows from a
+   cursor.  This is equivalent to the SQL command <command>FETCH</>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>Portal <parameter>portal</parameter></literal></term>
+    <listitem>
+     <para>
+      portal containing the cursor
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>FetchDirection <parameter>direction</parameter></literal></term>
+    <listitem>
+     <para>
+      one of <symbol>FETCH_FORWARD</symbol>,
+      <symbol>FETCH_BACKWARD</symbol>,
+      <symbol>FETCH_ABSOLUTE</symbol> or
+      <symbol>FETCH_RELATIVE</symbol>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>long <parameter>count</parameter></literal></term>
+    <listitem>
+     <para>
+      number of rows to fetch for
+      <symbol>FETCH_FORWARD</symbol> or
+      <symbol>FETCH_BACKWARD</symbol>; absolute row number to fetch for
+      <symbol>FETCH_ABSOLUTE</symbol>; or relative row number to fetch for
+      <symbol>FETCH_RELATIVE</symbol>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   <varname>SPI_processed</varname> and
+   <varname>SPI_tuptable</varname> are set as in
+   <function>SPI_execute</function> if successful.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   See the SQL <xref linkend="sql-fetch" endterm="sql-fetch-title"> command
+   for details of the interpretation of the 
+   <parameter>direction</parameter> and
+   <parameter>count</parameter> parameters.
+  </para>
+
+  <para>
+   Direction values other than <symbol>FETCH_FORWARD</symbol>
+   may fail if the cursor's plan was not created
+   with the <symbol>CURSOR_OPT_SCROLL</symbol> option.
+  </para>
+ </refsect1>
+</refentry>
+
+<!-- *********************************************** -->
+
+<refentry id="spi-spi-scroll-cursor-move">
+ <refmeta>
+  <refentrytitle>SPI_scroll_cursor_move</refentrytitle>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SPI_scroll_cursor_move</refname>
+  <refpurpose>move a cursor</refpurpose>
+ </refnamediv>
+
+ <indexterm><primary>SPI_scroll_cursor_move</primary></indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+void SPI_scroll_cursor_move(Portal <parameter>portal</parameter>, FetchDirection <parameter>direction</parameter>, long <parameter>count</parameter>)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <function>SPI_scroll_cursor_move</function> skips over some number of rows
+   in a cursor.  This is equivalent to the SQL command
+   <command>MOVE</>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Arguments</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>Portal <parameter>portal</parameter></literal></term>
+    <listitem>
+     <para>
+      portal containing the cursor
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>FetchDirection <parameter>direction</parameter></literal></term>
+    <listitem>
+     <para>
+      one of <symbol>FETCH_FORWARD</symbol>,
+      <symbol>FETCH_BACKWARD</symbol>,
+      <symbol>FETCH_ABSOLUTE</symbol> or
+      <symbol>FETCH_RELATIVE</symbol>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>long <parameter>count</parameter></literal></term>
+    <listitem>
+     <para>
+      number of rows to move for
+      <symbol>FETCH_FORWARD</symbol> or
+      <symbol>FETCH_BACKWARD</symbol>; absolute row number to move to for
+      <symbol>FETCH_ABSOLUTE</symbol>; or relative row number to move to for
+      <symbol>FETCH_RELATIVE</symbol>
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Return Value</title>
+
+  <para>
+   <varname>SPI_processed</varname> and
+   <varname>SPI_tuptable</varname> are set as in
+   <function>SPI_execute</function> if successful.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   See the SQL <xref linkend="sql-fetch" endterm="sql-fetch-title"> command
+   for details of the interpretation of the 
+   <parameter>direction</parameter> and
+   <parameter>count</parameter> parameters.
+  </para>
+
+  <para>
+   Direction values other than <symbol>FETCH_FORWARD</symbol>
+   may fail if the cursor's plan was not created
+   with the <symbol>CURSOR_OPT_SCROLL</symbol> option.
+  </para>
+ </refsect1>
 </refentry>
 
 <!-- *********************************************** -->
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 99d347f590..42121f3fe4 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.279 2007/03/29 00:15:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.280 2007/04/16 01:14:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1023,7 +1023,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 					 errmsg("COPY (SELECT INTO) is not supported")));
 
 		/* plan the query */
-		plan = planner(query, false, 0, NULL);
+		plan = planner(query, 0, NULL);
 
 		/*
 		 * Update snapshot command ID to ensure this query sees results of any
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1b2cfccce9..bb7d430359 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.160 2007/03/13 00:33:39 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.161 2007/04/16 01:14:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,7 +42,7 @@ typedef struct ExplainState
 	List	   *rtable;			/* range table */
 } ExplainState;
 
-static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
+static void ExplainOneQuery(Query *query, int cursorOptions,
 							ExplainStmt *stmt, const char *queryString,
 							ParamListInfo params, TupOutputState *tstate);
 static double elapsed_time(instr_time *starttime);
@@ -102,7 +102,7 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
 		/* Explain every plan */
 		foreach(l, rewritten)
 		{
-			ExplainOneQuery((Query *) lfirst(l), false, 0,
+			ExplainOneQuery((Query *) lfirst(l), 0,
 							stmt, queryString, params, tstate);
 			/* put a blank line between plans */
 			if (lnext(l) != NULL)
@@ -134,7 +134,7 @@ ExplainResultDesc(ExplainStmt *stmt)
  *	  print out the execution plan for one Query
  */
 static void
-ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
+ExplainOneQuery(Query *query, int cursorOptions,
 				ExplainStmt *stmt, const char *queryString,
 				ParamListInfo params, TupOutputState *tstate)
 {
@@ -150,7 +150,7 @@ ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
 	}
 
 	/* plan the query */
-	plan = planner(query, isCursor, cursorOptions, params);
+	plan = planner(query, cursorOptions, params);
 
 	/*
 	 * Update snapshot command ID to ensure this query sees results of any
@@ -229,7 +229,7 @@ ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
 		/* do not actually execute the underlying query! */
 		memcpy(&newstmt, stmt, sizeof(ExplainStmt));
 		newstmt.analyze = false;
-		ExplainOneQuery(query, true, dcstmt->options, &newstmt,
+		ExplainOneQuery(query, dcstmt->options, &newstmt,
 						queryString, params, tstate);
 	}
 	else if (IsA(utilityStmt, ExecuteStmt))
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index d759ed4ac2..eb381ebb70 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.63 2007/04/12 06:53:46 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.64 2007/04/16 01:14:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -111,7 +111,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
 				 errdetail("Cursors must be READ ONLY.")));
 
 	/* plan the query */
-	plan = planner(query, true, stmt->options, params);
+	plan = planner(query, stmt->options, params);
 
 	/*
 	 * Create a portal and copy the plan into its memory context.
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index fe1a8532f0..9a4f88d1a0 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.71 2007/04/12 06:53:46 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.72 2007/04/16 01:14:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -160,7 +160,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 	query_list = QueryRewrite(query);
 
 	/* Generate plans for queries.	Snapshot is already set. */
-	plan_list = pg_plan_queries(query_list, NULL, false);
+	plan_list = pg_plan_queries(query_list, 0, NULL, false);
 
 	/*
 	 * Save the results.
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 1e1d450be5..b59228b0c9 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.114 2007/04/02 18:49:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.115 2007/04/16 01:14:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -115,7 +115,7 @@ init_execution_state(List *queryTree_list, bool readonly_func)
 		if (queryTree->commandType == CMD_UTILITY)
 			stmt = queryTree->utilityStmt;
 		else
-			stmt = (Node *) pg_plan_query(queryTree, NULL);
+			stmt = (Node *) pg_plan_query(queryTree, 0, NULL);
 
 		/* Precheck all commands for validity in a function */
 		if (IsA(stmt, TransactionStmt))
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 655503cd70..bf3bbdbceb 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.175 2007/03/25 23:42:43 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.176 2007/04/16 01:14:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,7 +34,8 @@ static int	_SPI_stack_depth = 0;		/* allocated size of _SPI_stack */
 static int	_SPI_connected = -1;
 static int	_SPI_curid = -1;
 
-static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
+static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
+							  int cursorOptions);
 
 static int _SPI_execute_plan(SPIPlanPtr plan,
 				  Datum *Values, const char *Nulls,
@@ -45,8 +46,9 @@ static int	_SPI_pquery(QueryDesc *queryDesc, long tcount);
 
 static void _SPI_error_callback(void *arg);
 
-static void _SPI_cursor_operation(Portal portal, bool forward, long count,
-					  DestReceiver *dest);
+static void _SPI_cursor_operation(Portal portal,
+								  FetchDirection direction, long count,
+								  DestReceiver *dest);
 
 static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt);
 static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
@@ -310,7 +312,7 @@ SPI_execute(const char *src, bool read_only, long tcount)
 	memset(&plan, 0, sizeof(_SPI_plan));
 	plan.magic = _SPI_PLAN_MAGIC;
 
-	_SPI_prepare_plan(src, &plan);
+	_SPI_prepare_plan(src, &plan, 0);
 
 	res = _SPI_execute_plan(&plan, NULL, NULL,
 							InvalidSnapshot, InvalidSnapshot,
@@ -398,6 +400,13 @@ SPI_execute_snapshot(SPIPlanPtr plan,
 
 SPIPlanPtr
 SPI_prepare(const char *src, int nargs, Oid *argtypes)
+{
+	return SPI_prepare_cursor(src, nargs, argtypes, 0);
+}
+
+SPIPlanPtr
+SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
+				   int cursorOptions)
 {
 	_SPI_plan	plan;
 	SPIPlanPtr	result;
@@ -417,7 +426,7 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
 	plan.nargs = nargs;
 	plan.argtypes = argtypes;
 
-	_SPI_prepare_plan(src, &plan);
+	_SPI_prepare_plan(src, &plan, cursorOptions);
 
 	/* copy plan to procedure context */
 	result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
@@ -1032,7 +1041,8 @@ SPI_cursor_find(const char *name)
 void
 SPI_cursor_fetch(Portal portal, bool forward, long count)
 {
-	_SPI_cursor_operation(portal, forward, count,
+	_SPI_cursor_operation(portal,
+						  forward ? FETCH_FORWARD : FETCH_BACKWARD, count,
 						  CreateDestReceiver(DestSPI, NULL));
 	/* we know that the DestSPI receiver doesn't need a destroy call */
 }
@@ -1046,7 +1056,36 @@ SPI_cursor_fetch(Portal portal, bool forward, long count)
 void
 SPI_cursor_move(Portal portal, bool forward, long count)
 {
-	_SPI_cursor_operation(portal, forward, count, None_Receiver);
+	_SPI_cursor_operation(portal,
+						  forward ? FETCH_FORWARD : FETCH_BACKWARD, count,
+						  None_Receiver);
+}
+
+
+/*
+ * SPI_scroll_cursor_fetch()
+ *
+ *	Fetch rows in a scrollable cursor
+ */
+void
+SPI_scroll_cursor_fetch(Portal portal, FetchDirection direction, long count)
+{
+	_SPI_cursor_operation(portal,
+						  direction, count,
+						  CreateDestReceiver(DestSPI, NULL));
+	/* we know that the DestSPI receiver doesn't need a destroy call */
+}
+
+
+/*
+ * SPI_scroll_cursor_move()
+ *
+ *	Move in a scrollable cursor
+ */
+void
+SPI_scroll_cursor_move(Portal portal, FetchDirection direction, long count)
+{
+	_SPI_cursor_operation(portal, direction, count, None_Receiver);
 }
 
 
@@ -1299,7 +1338,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
  * and need to be copied somewhere to survive.
  */
 static void
-_SPI_prepare_plan(const char *src, SPIPlanPtr plan)
+_SPI_prepare_plan(const char *src, SPIPlanPtr plan, int cursorOptions)
 {
 	List	   *raw_parsetree_list;
 	List	   *plancache_list;
@@ -1345,7 +1384,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
 		/* Need a copyObject here to keep parser from modifying raw tree */
 		stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
 										   src, argtypes, nargs);
-		stmt_list = pg_plan_queries(stmt_list, NULL, false);
+		stmt_list = pg_plan_queries(stmt_list, cursorOptions, NULL, false);
 
 		plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
 		cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
@@ -1739,7 +1778,7 @@ _SPI_error_callback(void *arg)
  *	Do a FETCH or MOVE in a cursor
  */
 static void
-_SPI_cursor_operation(Portal portal, bool forward, long count,
+_SPI_cursor_operation(Portal portal, FetchDirection direction, long count,
 					  DestReceiver *dest)
 {
 	long		nfetched;
@@ -1760,7 +1799,7 @@ _SPI_cursor_operation(Portal portal, bool forward, long count,
 
 	/* Run the cursor */
 	nfetched = PortalRunFetch(portal,
-							  forward ? FETCH_FORWARD : FETCH_BACKWARD,
+							  direction,
 							  count,
 							  dest);
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9bf5516ca4..5c65a78d6f 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.216 2007/02/27 01:11:25 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.217 2007/04/16 01:14:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,8 +80,7 @@ static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
  *
  *****************************************************************************/
 PlannedStmt *
-planner(Query *parse, bool isCursor, int cursorOptions,
-		ParamListInfo boundParams)
+planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 {
 	PlannedStmt *result;
 	PlannerGlobal *glob;
@@ -107,7 +106,7 @@ planner(Query *parse, bool isCursor, int cursorOptions,
 	glob->finalrtable = NIL;
 
 	/* Determine what fraction of the plan is likely to be scanned */
-	if (isCursor)
+	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
 	{
 		/*
 		 * We have no real idea how many tuples the user will ultimately FETCH
@@ -130,7 +129,7 @@ planner(Query *parse, bool isCursor, int cursorOptions,
 	 * 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 (cursorOptions & CURSOR_OPT_SCROLL)
 	{
 		if (!ExecSupportsBackwardScan(top_plan))
 			top_plan = materialize_finished_plan(top_plan);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6aa81eec3e..b9728deaad 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.588 2007/04/12 06:53:46 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.589 2007/04/16 01:14:56 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -279,9 +279,9 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 %type <boolean> opt_freeze opt_default opt_recheck
 %type <defelt>	opt_binary opt_oids copy_delimiter
 
-%type <boolean> copy_from opt_hold
+%type <boolean> copy_from
 
-%type <ival>	opt_column event cursor_options
+%type <ival>	opt_column event cursor_options opt_hold
 %type <objtype>	reindex_type drop_type comment_type
 
 %type <node>	fetch_direction select_limit_value select_offset_value
@@ -5821,10 +5821,9 @@ DeclareCursorStmt: DECLARE name cursor_options CURSOR opt_hold FOR SelectStmt
 				{
 					DeclareCursorStmt *n = makeNode(DeclareCursorStmt);
 					n->portalname = $2;
-					n->options = $3;
+					/* currently we always set FAST_PLAN option */
+					n->options = $3 | $5 | CURSOR_OPT_FAST_PLAN;
 					n->query = $7;
-					if ($5)
-						n->options |= CURSOR_OPT_HOLD;
 					$$ = (Node *)n;
 				}
 		;
@@ -5836,9 +5835,9 @@ cursor_options: /*EMPTY*/					{ $$ = 0; }
 			| cursor_options INSENSITIVE	{ $$ = $1 | CURSOR_OPT_INSENSITIVE; }
 		;
 
-opt_hold: /* EMPTY */						{ $$ = FALSE; }
-			| WITH HOLD						{ $$ = TRUE; }
-			| WITHOUT HOLD					{ $$ = FALSE; }
+opt_hold: /* EMPTY */						{ $$ = 0; }
+			| WITH HOLD						{ $$ = CURSOR_OPT_HOLD; }
+			| WITHOUT HOLD					{ $$ = 0; }
 		;
 
 /*****************************************************************************
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bfcc271996..7722eddf34 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.530 2007/03/29 19:10:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.531 2007/04/16 01:14:57 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -656,9 +656,12 @@ pg_rewrite_queries(List *querytree_list)
 }
 
 
-/* Generate a plan for a single already-rewritten query. */
+/*
+ * Generate a plan for a single already-rewritten query.
+ * This is a thin wrapper around planner() and takes the same parameters.
+ */
 PlannedStmt *
-pg_plan_query(Query *querytree, ParamListInfo boundParams)
+pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
 {
 	PlannedStmt *plan;
 
@@ -670,7 +673,7 @@ pg_plan_query(Query *querytree, ParamListInfo boundParams)
 		ResetUsage();
 
 	/* call the optimizer */
-	plan = planner(querytree, false, 0, boundParams);
+	plan = planner(querytree, cursorOptions, boundParams);
 
 	if (log_planner_stats)
 		ShowUsage("PLANNER STATISTICS");
@@ -718,7 +721,7 @@ pg_plan_query(Query *querytree, ParamListInfo boundParams)
  * list.  Utility statements are simply represented by their statement nodes.
  */
 List *
-pg_plan_queries(List *querytrees, ParamListInfo boundParams,
+pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams,
 				bool needSnapshot)
 {
 	List	   *stmt_list = NIL;
@@ -741,7 +744,7 @@ pg_plan_queries(List *querytrees, ParamListInfo boundParams,
 				ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
 				needSnapshot = false;
 			}
-			stmt = (Node *) pg_plan_query(query, boundParams);
+			stmt = (Node *) pg_plan_query(query, cursorOptions, boundParams);
 		}
 
 		stmt_list = lappend(stmt_list, stmt);
@@ -892,7 +895,7 @@ exec_simple_query(const char *query_string)
 		querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
 												NULL, 0);
 
-		plantree_list = pg_plan_queries(querytree_list, NULL, true);
+		plantree_list = pg_plan_queries(querytree_list, 0, NULL, true);
 
 		/* If we got a cancel signal in analysis or planning, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -1207,7 +1210,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 		}
 		else
 		{
-			stmt_list = pg_plan_queries(querytree_list, NULL, true);
+			stmt_list = pg_plan_queries(querytree_list, 0, NULL, true);
 			fully_planned = true;
 		}
 	}
@@ -1621,7 +1624,7 @@ exec_bind_message(StringInfo input_message)
 		 */
 		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 		query_list = copyObject(cplan->stmt_list);
-		plan_list = pg_plan_queries(query_list, params, true);
+		plan_list = pg_plan_queries(query_list, 0, params, true);
 		MemoryContextSwitchTo(oldContext);
 
 		/* We no longer need the cached plan refcount ... */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 6165e59c6e..28838eba09 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -33,7 +33,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.6 2007/04/12 06:53:47 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.7 2007/04/16 01:14:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -458,10 +458,13 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
 		if (plansource->fully_planned)
 		{
 			/*
-			 * Generate plans for queries.	Assume snapshot is not set yet
+			 * Generate plans for queries.  We don't need any boundParams, and
+			 * currently we don't need to worry about cursor options because
+			 * cursor plans are never saved in the plancache (that might have
+			 * to change someday).  Also, assume snapshot is not set yet
 			 * (XXX this may be wasteful, won't all callers have done that?)
 			 */
-			slist = pg_plan_queries(slist, NULL, true);
+			slist = pg_plan_queries(slist, 0, NULL, true);
 		}
 
 		/*
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 80c8993c7c..c996f644c0 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.60 2007/03/25 23:27:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.61 2007/04/16 01:14:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,8 +20,8 @@
 #include "postgres.h"
 
 /*
- *	These are not needed by this file, but used by other programs
- *	using SPI
+ *	Most of these are not needed by this file, but may be used by
+ *	user-written code that uses SPI
  */
 #include "access/heapam.h"
 #include "access/xact.h"
@@ -32,6 +32,7 @@
 #include "executor/executor.h"
 #include "nodes/execnodes.h"
 #include "nodes/params.h"
+#include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
 #include "nodes/primnodes.h"
 #include "nodes/relation.h"
@@ -105,6 +106,8 @@ extern int	SPI_execute_snapshot(SPIPlanPtr plan,
 					 Snapshot crosscheck_snapshot,
 					 bool read_only, long tcount);
 extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
+extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
+									 int cursorOptions);
 extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
 extern int	SPI_freeplan(SPIPlanPtr plan);
 
@@ -136,6 +139,8 @@ extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
 extern Portal SPI_cursor_find(const char *name);
 extern void SPI_cursor_fetch(Portal portal, bool forward, long count);
 extern void SPI_cursor_move(Portal portal, bool forward, long count);
+extern void SPI_scroll_cursor_fetch(Portal, FetchDirection direction, long count);
+extern void SPI_scroll_cursor_move(Portal, FetchDirection direction, long count);
 extern void SPI_cursor_close(Portal portal);
 
 extern void AtEOXact_SPI(bool isCommit);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc421e265f..dbc29642f0 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.345 2007/04/12 06:53:48 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.346 2007/04/16 01:14:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1429,11 +1429,12 @@ typedef struct CommentStmt
  *		Declare Cursor Statement
  * ----------------------
  */
-#define CURSOR_OPT_BINARY		0x0001
-#define CURSOR_OPT_SCROLL		0x0002
-#define CURSOR_OPT_NO_SCROLL	0x0004
-#define CURSOR_OPT_INSENSITIVE	0x0008
-#define CURSOR_OPT_HOLD			0x0010
+#define CURSOR_OPT_BINARY		0x0001		/* BINARY */
+#define CURSOR_OPT_SCROLL		0x0002		/* SCROLL explicitly given */
+#define CURSOR_OPT_NO_SCROLL	0x0004		/* NO SCROLL explicitly given */
+#define CURSOR_OPT_INSENSITIVE	0x0008		/* INSENSITIVE (unimplemented) */
+#define CURSOR_OPT_HOLD			0x0010		/* WITH HOLD */
+#define CURSOR_OPT_FAST_PLAN	0x0020		/* prefer fast-start plan */
 
 typedef struct DeclareCursorStmt
 {
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index c243cdbc35..b568a7dbe8 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.38 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.39 2007/04/16 01:14:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,7 @@
 #include "nodes/relation.h"
 
 
-extern PlannedStmt *planner(Query *parse, bool isCursor, int cursorOptions,
+extern PlannedStmt *planner(Query *parse, int cursorOptions,
 		ParamListInfo boundParams);
 extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 							  Index level, double tuple_fraction,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 098cb1da1e..613d9a9983 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.88 2007/03/07 13:35:03 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.89 2007/04/16 01:14:58 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -49,9 +49,10 @@ extern List *pg_parse_and_rewrite(const char *query_string,
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
 					   Oid *paramTypes, int numParams);
-extern PlannedStmt *pg_plan_query(Query *querytree, ParamListInfo boundParams);
-extern List *pg_plan_queries(List *querytrees, ParamListInfo boundParams,
-				bool needSnapshot);
+extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
+								  ParamListInfo boundParams);
+extern List *pg_plan_queries(List *querytrees, int cursorOptions,
+							 ParamListInfo boundParams, bool needSnapshot);
 
 extern bool assign_max_stack_depth(int newval, bool doit, GucSource source);