diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml
index 4afe2d03b255ec3f1f2fa2605bc1eb158893e053..f823cf77bbea5a31c1e634871d9bd2953d2aa8ee 100644
--- a/doc/src/sgml/ref/declare.sgml
+++ b/doc/src/sgml/ref/declare.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/declare.sgml,v 1.40 2007/01/31 23:26:03 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/declare.sgml,v 1.41 2007/06/11 01:16:21 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -27,7 +27,6 @@ PostgreSQL documentation
 <synopsis>
 DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ]
     CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="parameter">query</replaceable>
-    [ FOR { READ ONLY | UPDATE [ OF <replaceable class="parameter">column</replaceable> [, ...] ] } ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -37,50 +36,10 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
   <para>
    <command>DECLARE</command> allows a user to create cursors, which
    can be used to retrieve
-   a small number of rows at a time out of a larger query. Cursors can
-   return data either in text or in binary format using
+   a small number of rows at a time out of a larger query.
+   After the cursor is created, rows are fetched from it using
    <xref linkend="sql-fetch" endterm="sql-fetch-title">.
   </para>
-
-  <para>
-   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.  Once the information comes back in text form, the client
-   application might need to convert it to a binary format to manipulate
-   it.  In addition, data in the text format is often larger in size
-   than in the binary format.  Binary cursors return the data in a
-   binary representation that might be more easily manipulated.
-   Nevertheless, if you intend to display the data as text anyway,
-   retrieving it in text form will
-   save you some effort on the client side.
-  </para>
-
-  <para>
-   As an example, if a query returns a value of one from an integer column,
-   you would get a string of <literal>1</> with a default cursor
-   whereas with a binary cursor you would get
-   a 4-byte field containing the internal representation of the value
-   (in big-endian byte order).
-  </para>
-
-  <para>
-   Binary cursors should be used carefully.  Many applications,
-   including <application>psql</application>, are not prepared to
-   handle binary cursors and expect data to come back in the text
-   format.
-  </para>
-
-  <note>
-   <para>
-    When the client application uses the <quote>extended query</> protocol
-    to issue a <command>FETCH</> command, the Bind protocol message
-    specifies whether data is to be retrieved in text or binary format.
-    This choice overrides the way that the cursor is defined.  The concept
-    of a binary cursor as such is thus obsolete when using extended query
-    protocol &mdash; any cursor can be treated as either text or binary.
-   </para>
-  </note>
  </refsect1>
 
  <refsect1>
@@ -110,10 +69,10 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
     <listitem>
      <para>
       Indicates that data retrieved from the cursor should be
-      unaffected by updates to the tables underlying the cursor while
-      the cursor exists.  In <productname>PostgreSQL</productname>,
-      all cursors are insensitive; this key word currently has no
-      effect and is present for compatibility with the SQL standard.
+      unaffected by updates to the table(s) underlying the cursor that occur
+      after the cursor is created.  In <productname>PostgreSQL</productname>,
+      this is the default behavior; so this key word has no
+      effect and is only accepted for compatibility with the SQL standard.
      </para>
     </listitem>
    </varlistentry>
@@ -163,34 +122,6 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
      </para>
     </listitem>
    </varlistentry>
-
-   <varlistentry>
-    <term><literal>FOR READ ONLY</literal></term>
-    <term><literal>FOR UPDATE</literal></term>
-    <listitem>
-     <para>
-      <literal>FOR READ ONLY</literal> indicates that the cursor will
-      be used in a read-only mode.  <literal>FOR UPDATE</literal>
-      indicates that the cursor will be used to update tables.  Since
-      cursor updates are not currently supported in
-      <productname>PostgreSQL</productname>, specifying <literal>FOR
-      UPDATE</literal> will cause an error message and specifying
-      <literal>FOR READ ONLY</literal> has no effect.
-     </para>
-    </listitem>
-   </varlistentry>
-
-   <varlistentry>
-    <term><replaceable class="parameter">column</replaceable></term>
-    <listitem>
-     <para>
-      Column(s) to be updated by the cursor.  Since cursor updates are
-      not currently supported in
-      <productname>PostgreSQL</productname>, the <literal>FOR
-      UPDATE</literal> clause provokes an error message.
-     </para>
-    </listitem>
-   </varlistentry>
   </variablelist>
 
   <para>
@@ -203,6 +134,38 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
  <refsect1 id="sql-declare-notes">
   <title id="sql-declare-notes-title">Notes</title>
 
+  <para>
+   Normal cursors return data in text format, the same as a
+   <command>SELECT</> would produce.  The <literal>BINARY</> option
+   specifies that the cursor should return data in binary format.
+   This reduces conversion effort for both the server and client,
+   at the cost of more programmer effort to deal with platform-dependent
+   binary data formats.
+   As an example, if a query returns a value of one from an integer column,
+   you would get a string of <literal>1</> with a default cursor,
+   whereas with a binary cursor you would get
+   a 4-byte field containing the internal representation of the value
+   (in big-endian byte order).
+  </para>
+
+  <para>
+   Binary cursors should be used carefully.  Many applications,
+   including <application>psql</application>, are not prepared to
+   handle binary cursors and expect data to come back in the text
+   format.
+  </para>
+
+  <note>
+   <para>
+    When the client application uses the <quote>extended query</> protocol
+    to issue a <command>FETCH</> command, the Bind protocol message
+    specifies whether data is to be retrieved in text or binary format.
+    This choice overrides the way that the cursor is defined.  The concept
+    of a binary cursor as such is thus obsolete when using extended query
+    protocol &mdash; any cursor can be treated as either text or binary.
+   </para>
+  </note>
+
    <para>
     Unless <literal>WITH HOLD</literal> is specified, the cursor
     created by this command can only be used within the current
@@ -232,6 +195,11 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
     transactions.
    </para>
 
+   <para>
+    <literal>WITH HOLD</literal> may not be specified when the query
+    includes <literal>FOR UPDATE</> or <literal>FOR SHARE</>.
+   </para>
+
    <para>
     The <literal>SCROLL</> option should be specified when defining a
     cursor that will be used to fetch backwards.  This is required by
@@ -245,6 +213,23 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
     specified, then backward fetches are disallowed in any case.
    </para>
 
+   <para>
+    If the cursor's query includes <literal>FOR UPDATE</> or <literal>FOR
+    SHARE</>, then returned rows are locked at the time they are first
+    fetched, in the same way as for a regular
+    <xref linkend="sql-select" endterm="sql-select-title"> command with
+    these options.
+    In addition, the returned rows will be the most up-to-date versions;
+    therefore these options provide the equivalent of what the SQL standard
+    calls a <quote>sensitive cursor</>.  It is often wise to use <literal>FOR
+    UPDATE</> if the cursor is intended to be used with <command>UPDATE
+    ... WHERE CURRENT OF</> or <command>DELETE ... WHERE CURRENT OF</>,
+    since this will prevent other sessions from changing the rows between
+    the time they are fetched and the time they are updated.  Without
+    <literal>FOR UPDATE</>, a subsequent <literal>WHERE CURRENT OF</> command
+    will have no effect if the row was changed meanwhile.
+   </para>
+
    <para>
     The SQL standard only makes provisions for cursors in embedded
     <acronym>SQL</acronym>.  The <productname>PostgreSQL</productname>
@@ -280,14 +265,16 @@ DECLARE liahona CURSOR FOR SELECT * FROM films;
   <title>Compatibility</title>
 
   <para>
-   The SQL standard allows cursors only in embedded
-   <acronym>SQL</acronym> and in modules. <productname>PostgreSQL</>
-   permits cursors to be used interactively.
+   The SQL standard specifies that by default, cursors are sensitive to
+   concurrent updates of the underlying data.  In
+   <productname>PostgreSQL</productname>, cursors are insensitive by default,
+   and can be made sensitive by specifying <literal>FOR UPDATE</>.
   </para>
 
   <para>
-   The SQL standard allows cursors to update table data. All
-   <productname>PostgreSQL</> cursors are read only.
+   The SQL standard allows cursors only in embedded
+   <acronym>SQL</acronym> and in modules. <productname>PostgreSQL</>
+   permits cursors to be used interactively.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml
index f57c56ecf7e1f50bbd859de305bf584889288146..b5c7d97f524bc005793ace4c35254a04695ab2e7 100644
--- a/doc/src/sgml/ref/delete.sgml
+++ b/doc/src/sgml/ref/delete.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/delete.sgml,v 1.30 2007/02/01 00:28:19 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/delete.sgml,v 1.31 2007/06/11 01:16:21 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -22,7 +22,7 @@ PostgreSQL documentation
 <synopsis>
 DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
     [ USING <replaceable class="PARAMETER">usinglist</replaceable> ]
-    [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
+    [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
     [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ AS <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
 </synopsis>
  </refsynopsisdiv>
@@ -134,9 +134,23 @@ DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ]
     <term><replaceable class="parameter">condition</replaceable></term>
     <listitem>
      <para>
-      An expression returning a value of type
-      <type>boolean</type>, which determines the rows that are to be
-      deleted.
+      An expression that returns a value of type <type>boolean</type>.
+      Only rows for which this expression returns <literal>true</>
+      will be deleted.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">cursor_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the cursor to use in a <literal>WHERE CURRENT OF</>
+      condition.  The row to be deleted is the one most recently fetched
+      from this cursor.  The cursor must be a simple (non-join, non-aggregate)
+      query on the <command>DELETE</>'s target table.
+      Note that <literal>WHERE CURRENT OF</> cannot be
+      specified together with a boolean condition.
      </para>
     </listitem>
    </varlistentry>
@@ -236,6 +250,14 @@ DELETE FROM films;
    Delete completed tasks, returning full details of the deleted rows:
 <programlisting>
 DELETE FROM tasks WHERE status = 'DONE' RETURNING *;
+</programlisting>      
+  </para>
+
+   <para>
+   Delete the row of <structname>tasks</> on which the cursor
+   <literal>c_tasks</> is currently positioned:
+<programlisting>
+DELETE FROM tasks WHERE CURRENT OF c_tasks;
 </programlisting>      
   </para>
  </refsect1>
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index df0bec9a1d69452fb0202316951b4aa8f32630e0..2c6a480c4a525a89df6c32621d77b90180b103e6 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.43 2007/02/01 00:28:19 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.44 2007/06/11 01:16:22 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -24,7 +24,7 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
     SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
           ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...]
     [ FROM <replaceable class="PARAMETER">fromlist</replaceable> ]
-    [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
+    [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ]
     [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ AS <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
 </synopsis>
  </refsynopsisdiv>
@@ -160,6 +160,20 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="PARAMETER">cursor_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the cursor to use in a <literal>WHERE CURRENT OF</>
+      condition.  The row to be updated is the one most recently fetched
+      from this cursor.  The cursor must be a simple (non-join, non-aggregate)
+      query on the <command>UPDATE</>'s target table.
+      Note that <literal>WHERE CURRENT OF</> cannot be
+      specified together with a boolean condition.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="PARAMETER">output_expression</replaceable></term>
     <listitem>
@@ -309,6 +323,15 @@ UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
 COMMIT;
 </programlisting>
   </para>
+
+  <para>
+   Change the <structfield>kind</> column of the table
+   <structname>films</structname> in the row on which the cursor
+   <literal>c_films</> is currently positioned:
+<programlisting>
+UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
+</programlisting>      
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 55256d9e469629e5989c7fed8fe1da021a374607..cb4ab9dc31a1430226bbe6cb34a3b8baf9d5195d 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for executor
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.25 2007/01/20 17:16:11 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.26 2007/06/11 01:16:22 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -12,7 +12,7 @@ subdir = src/backend/executor
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = execAmi.o execGrouping.o execJunk.o execMain.o \
+OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        execProcnode.o execQual.o execScan.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
        nodeBitmapAnd.o nodeBitmapOr.o \
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
new file mode 100644
index 0000000000000000000000000000000000000000..ce95d58b81bbf528d960e8cb641fff2f27eb27b2
--- /dev/null
+++ b/src/backend/executor/execCurrent.c
@@ -0,0 +1,185 @@
+/*-------------------------------------------------------------------------
+ *
+ * execCurrent.c
+ *	  executor support for WHERE CURRENT OF cursor
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *	$PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.1 2007/06/11 01:16:22 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "utils/lsyscache.h"
+#include "utils/portal.h"
+
+
+static ScanState *search_plan_tree(PlanState *node, Oid table_oid);
+
+
+/*
+ * execCurrentOf
+ *
+ * Given the name of a cursor and the OID of a table, determine which row
+ * of the table is currently being scanned by the cursor, and return its
+ * TID into *current_tid.
+ *
+ * Returns TRUE if a row was identified.  Returns FALSE if the cursor is valid
+ * for the table but is not currently scanning a row of the table (this is a
+ * legal situation in inheritance cases).  Raises error if cursor is not a
+ * valid updatable scan of the specified table.
+ */
+bool
+execCurrentOf(char *cursor_name, Oid table_oid,
+			  ItemPointer current_tid)
+{
+	char	   *table_name;
+	Portal		portal;
+	QueryDesc *queryDesc;
+	ScanState  *scanstate;
+	HeapTuple tup;
+
+	/* Fetch table name for possible use in error messages */
+	table_name = get_rel_name(table_oid);
+	if (table_name == NULL)
+		elog(ERROR, "cache lookup failed for relation %u", table_oid);
+
+	/* Find the cursor's portal */
+	portal = GetPortalByName(cursor_name);
+	if (!PortalIsValid(portal))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_CURSOR),
+				 errmsg("cursor \"%s\" does not exist", cursor_name)));
+
+	/*
+	 * We have to watch out for non-SELECT queries as well as held cursors,
+	 * both of which may have null queryDesc.
+	 */
+	if (portal->strategy != PORTAL_ONE_SELECT)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_STATE),
+				 errmsg("cursor \"%s\" is not a SELECT query",
+						cursor_name)));
+	queryDesc = PortalGetQueryDesc(portal);
+	if (queryDesc == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_STATE),
+				 errmsg("cursor \"%s\" is held from a previous transaction",
+						cursor_name)));
+
+	/*
+	 * Dig through the cursor's plan to find the scan node.  Fail if it's
+	 * not there or buried underneath aggregation.
+	 */
+	scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc),
+								 table_oid);
+	if (!scanstate)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_STATE),
+				 errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"",
+						cursor_name, table_name)));
+
+	/*
+	 * The cursor must have a current result row: per the SQL spec, it's
+	 * an error if not.  We test this at the top level, rather than at
+	 * the scan node level, because in inheritance cases any one table
+	 * scan could easily not be on a row.  We want to return false, not
+	 * raise error, if the passed-in table OID is for one of the inactive
+	 * scans.
+	 */
+	if (portal->atStart || portal->atEnd)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_CURSOR_STATE),
+				 errmsg("cursor \"%s\" is not positioned on a row",
+						cursor_name)));
+
+	/* Now OK to return false if we found an inactive scan */
+	if (TupIsNull(scanstate->ss_ScanTupleSlot))
+		return false;
+
+	tup = scanstate->ss_ScanTupleSlot->tts_tuple;
+	if (tup == NULL)
+		elog(ERROR, "CURRENT OF applied to non-materialized tuple");
+	Assert(tup->t_tableOid == table_oid);
+
+	*current_tid = tup->t_self;
+
+	return true;
+}
+
+/*
+ * search_plan_tree
+ *
+ * Search through a PlanState tree for a scan node on the specified table.
+ * Return NULL if not found or multiple candidates.
+ */
+static ScanState *
+search_plan_tree(PlanState *node, Oid table_oid)
+{
+	if (node == NULL)
+		return NULL;
+	switch (nodeTag(node))
+	{
+			/*
+			 * scan nodes can all be treated alike
+			 */
+		case T_SeqScanState:
+		case T_IndexScanState:
+		case T_BitmapHeapScanState:
+		case T_TidScanState:
+		{
+			ScanState *sstate = (ScanState *) node;
+
+			if (RelationGetRelid(sstate->ss_currentRelation) == table_oid)
+				return sstate;
+			break;
+		}
+
+			/*
+			 * For Append, we must look through the members; watch out for
+			 * multiple matches (possible if it was from UNION ALL)
+			 */
+		case T_AppendState:
+		{
+			AppendState *astate = (AppendState *) node;
+			ScanState *result = NULL;
+			int		i;
+
+			for (i = 0; i < astate->as_nplans; i++)
+			{
+				ScanState *elem = search_plan_tree(astate->appendplans[i],
+												   table_oid);
+
+				if (!elem)
+					continue;
+				if (result)
+					return NULL;				/* multiple matches */
+				result = elem;
+			}
+			return result;
+		}
+
+			/*
+			 * Result and Limit can be descended through (these are safe
+			 * because they always return their input's current row)
+			 */
+		case T_ResultState:
+		case T_LimitState:
+			return search_plan_tree(node->lefttree, table_oid);
+
+			/*
+			 * SubqueryScan too, but it keeps the child in a different place
+			 */
+		case T_SubqueryScanState:
+			return search_plan_tree(((SubqueryScanState *) node)->subplan,
+									table_oid);
+
+		default:
+			/* Otherwise, assume we can't descend through it */
+			break;
+	}
+	return NULL;
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 82b17b19f0112e25275ae026fc8babee129bc336..dfd1a84ab77f90b1e09512a87f985296a6e64de0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.294 2007/06/03 17:07:00 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.295 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2368,6 +2368,24 @@ EvalPlanQualStop(evalPlanQual *epq)
 	epq->planstate = NULL;
 }
 
+/*
+ * ExecGetActivePlanTree --- get the active PlanState tree from a QueryDesc
+ *
+ * Ordinarily this is just the one mentioned in the QueryDesc, but if we
+ * are looking at a row returned by the EvalPlanQual machinery, we need
+ * to look at the subsidiary state instead.
+ */
+PlanState *
+ExecGetActivePlanTree(QueryDesc *queryDesc)
+{
+	EState	   *estate = queryDesc->estate;
+
+	if (estate && estate->es_useEvalPlan && estate->es_evalPlanQual != NULL)
+		return estate->es_evalPlanQual->planstate;
+	else
+		return queryDesc->planstate;
+}
+
 
 /*
  * Support for SELECT INTO (a/k/a CREATE TABLE AS)
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index fd54b89f0a5c401d4fdad89a6616a41fe1c76594..5549142e703287324426f3d9c132813b4be8f663 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.218 2007/06/05 21:31:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.219 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -151,6 +151,8 @@ static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
 static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
 									 ExprContext *econtext,
 									 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
+			bool *isNull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -3618,6 +3620,41 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
 					 astate->amstate);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalCurrentOfExpr
+ *
+ * Normally, the planner will convert CURRENT OF into a TidScan qualification,
+ * but we have plain execQual support in case it doesn't.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
+					  bool *isNull, ExprDoneCond *isDone)
+{
+	CurrentOfExpr *cexpr = (CurrentOfExpr *) exprstate->expr;
+	bool result;
+	HeapTuple tup;
+	ItemPointerData cursor_tid;
+
+	if (isDone)
+		*isDone = ExprSingleResult;
+	*isNull = false;
+
+	Assert(cexpr->cvarno != INNER);
+	Assert(cexpr->cvarno != OUTER);
+	Assert(!TupIsNull(econtext->ecxt_scantuple));
+	tup = econtext->ecxt_scantuple->tts_tuple;
+	if (tup == NULL)
+		elog(ERROR, "CURRENT OF applied to non-materialized tuple");
+
+	if (execCurrentOf(cexpr->cursor_name, tup->t_tableOid, &cursor_tid))
+		result = ItemPointerEquals(&cursor_tid, &(tup->t_self));
+	else
+		result = false;
+
+	return BoolGetDatum(result);
+}
+
 
 /*
  * ExecEvalExprSwitchContext
@@ -4266,6 +4303,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) cstate;
 			}
 			break;
+		case T_CurrentOfExpr:
+			state = (ExprState *) makeNode(ExprState);
+			state->evalfunc = ExecEvalCurrentOfExpr;
+			break;
 		case T_TargetEntry:
 			{
 				TargetEntry *tle = (TargetEntry *) node;
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 9b6724537e70b6e3522509db79bf951143fd30f7..986ff1f3f197edbf84990cf4183250bf34a92669 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.53 2007/01/05 22:19:28 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.54 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,8 +61,8 @@ TidListCreate(TidScanState *tidstate)
 
 	/*
 	 * We initialize the array with enough slots for the case that all quals
-	 * are simple OpExprs.	If there's any ScalarArrayOpExprs, we may have to
-	 * enlarge the array.
+	 * are simple OpExprs or CurrentOfExprs.  If there are any
+	 * ScalarArrayOpExprs, we may have to enlarge the array.
 	 */
 	numAllocTids = list_length(evalList);
 	tidList = (ItemPointerData *)
@@ -148,6 +148,25 @@ TidListCreate(TidScanState *tidstate)
 			pfree(ipdatums);
 			pfree(ipnulls);
 		}
+		else if (expr && IsA(expr, CurrentOfExpr))
+		{
+			CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
+			ItemPointerData cursor_tid;
+
+			if (execCurrentOf(cexpr->cursor_name,
+						  RelationGetRelid(tidstate->ss.ss_currentRelation),
+							  &cursor_tid))
+			{
+				if (numTids >= numAllocTids)
+				{
+					numAllocTids *= 2;
+					tidList = (ItemPointerData *)
+						repalloc(tidList,
+								 numAllocTids * sizeof(ItemPointerData));
+				}
+				tidList[numTids++] = cursor_tid;
+			}
+		}
 		else
 			elog(ERROR, "could not identify CTID expression");
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 39027b1dbc84706eb2cce718360184efadd52c46..c868ff574d9357aa30d7cf3187ba4fce95d4d478 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.377 2007/06/05 21:31:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.378 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1299,6 +1299,20 @@ _copySetToDefault(SetToDefault *from)
 	return newnode;
 }
 
+/*
+ * _copyCurrentOfExpr
+ */
+static CurrentOfExpr *
+_copyCurrentOfExpr(CurrentOfExpr *from)
+{
+	CurrentOfExpr *newnode = makeNode(CurrentOfExpr);
+
+	COPY_SCALAR_FIELD(cvarno);
+	COPY_STRING_FIELD(cursor_name);
+
+	return newnode;
+}
+
 /*
  * _copyTargetEntry
  */
@@ -3177,6 +3191,9 @@ copyObject(void *from)
 		case T_SetToDefault:
 			retval = _copySetToDefault(from);
 			break;
+		case T_CurrentOfExpr:
+			retval = _copyCurrentOfExpr(from);
+			break;
 		case T_TargetEntry:
 			retval = _copyTargetEntry(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a0957c117e5dad4578e5420e93432aed54d6aee..04072c7a65422560271d422496ac86230e40f817 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.308 2007/06/05 21:31:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.309 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -598,6 +598,15 @@ _equalSetToDefault(SetToDefault *a, SetToDefault *b)
 	return true;
 }
 
+static bool
+_equalCurrentOfExpr(CurrentOfExpr *a, CurrentOfExpr *b)
+{
+	COMPARE_SCALAR_FIELD(cvarno);
+	COMPARE_STRING_FIELD(cursor_name);
+
+	return true;
+}
+
 static bool
 _equalTargetEntry(TargetEntry *a, TargetEntry *b)
 {
@@ -2124,6 +2133,9 @@ equal(void *a, void *b)
 		case T_SetToDefault:
 			retval = _equalSetToDefault(a, b);
 			break;
+		case T_CurrentOfExpr:
+			retval = _equalCurrentOfExpr(a, b);
+			break;
 		case T_TargetEntry:
 			retval = _equalTargetEntry(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5de540642f482fa2b14a1ce96abc368ff3f1850f..869905f0cc548e01b42ecf74f314bbc850b43597 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.309 2007/06/05 21:31:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.310 2007/06/11 01:16:22 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1058,6 +1058,15 @@ _outSetToDefault(StringInfo str, SetToDefault *node)
 	WRITE_INT_FIELD(typeMod);
 }
 
+static void
+_outCurrentOfExpr(StringInfo str, CurrentOfExpr *node)
+{
+	WRITE_NODE_TYPE("CURRENTOFEXPR");
+
+	WRITE_UINT_FIELD(cvarno);
+	WRITE_STRING_FIELD(cursor_name);
+}
+
 static void
 _outTargetEntry(StringInfo str, TargetEntry *node)
 {
@@ -2229,6 +2238,9 @@ _outNode(StringInfo str, void *obj)
 			case T_SetToDefault:
 				_outSetToDefault(str, obj);
 				break;
+			case T_CurrentOfExpr:
+				_outCurrentOfExpr(str, obj);
+				break;
 			case T_TargetEntry:
 				_outTargetEntry(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 86c9e911a7c8dfbdb2fa11b49e06c97c159a9af8..e91a6e5b501a92b83593d772ea9b1a05b388da1f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.207 2007/06/05 21:31:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.208 2007/06/11 01:16:22 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -873,6 +873,20 @@ _readSetToDefault(void)
 	READ_DONE();
 }
 
+/*
+ * _readCurrentOfExpr
+ */
+static CurrentOfExpr *
+_readCurrentOfExpr(void)
+{
+	READ_LOCALS(CurrentOfExpr);
+
+	READ_UINT_FIELD(cvarno);
+	READ_STRING_FIELD(cursor_name);
+
+	READ_DONE();
+}
+
 /*
  * _readTargetEntry
  */
@@ -1093,6 +1107,8 @@ parseNodeString(void)
 		return_value = _readCoerceToDomainValue();
 	else if (MATCH("SETTODEFAULT", 12))
 		return_value = _readSetToDefault();
+	else if (MATCH("CURRENTOFEXPR", 13))
+		return_value = _readCurrentOfExpr();
 	else if (MATCH("TARGETENTRY", 11))
 		return_value = _readTargetEntry();
 	else if (MATCH("RANGETBLREF", 11))
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index b8bbc29c505bb3c418144644d8d0cac819003d59..4b48ae1e26072fcae607a3db2fe179d98d4ccc26 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.85 2007/04/21 21:01:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.86 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,7 @@
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
+#include "optimizer/pathnode.h"
 #include "optimizer/plancat.h"
 #include "parser/parsetree.h"
 #include "utils/fmgroids.h"
@@ -712,6 +713,15 @@ clause_selectivity(PlannerInfo *root,
 						 varRelid,
 						 jointype);
 	}
+	else if (IsA(clause, CurrentOfExpr))
+	{
+		/* CURRENT OF selects at most one row of its table */
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) clause;
+		RelOptInfo *crel = find_base_rel(root, cexpr->cvarno);
+
+		if (crel->tuples > 0)
+			s1 = 1.0 / crel->tuples;
+	}
 	else if (IsA(clause, RelabelType))
 	{
 		/* Not sure this case is needed, but it can't hurt */
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a4d03e9f8f8d6071386736ec0c935b37092a17ed..f76c778998b26c111b362e77bce57a68fa26cbbb 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -54,7 +54,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.184 2007/06/05 21:31:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.185 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -770,6 +770,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	Cost		startup_cost = 0;
 	Cost		run_cost = 0;
 	Cost		cpu_per_tuple;
+	QualCost	tid_qual_cost;
 	int			ntuples;
 	ListCell   *l;
 
@@ -799,12 +800,20 @@ cost_tidscan(Path *path, PlannerInfo *root,
 		}
 	}
 
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per retrived tuple.
+	 */
+	cost_qual_eval(&tid_qual_cost, tidquals, root);
+
 	/* disk costs --- assume each tuple on a different page */
 	run_cost += random_page_cost * ntuples;
 
 	/* CPU costs */
-	startup_cost += baserel->baserestrictcost.startup;
-	cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+	startup_cost += baserel->baserestrictcost.startup +
+		tid_qual_cost.per_tuple;
+	cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple -
+		tid_qual_cost.per_tuple;
 	run_cost += cpu_per_tuple * ntuples;
 
 	path->startup_cost = startup_cost;
@@ -1991,6 +2000,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 				cpu_operator_cost;
 		}
 	}
+	else if (IsA(node, CurrentOfExpr))
+	{
+		/* This is noticeably more expensive than a typical operator */
+		context->total.per_tuple += 100 * cpu_operator_cost;
+	}
 	else if (IsA(node, SubLink))
 	{
 		/* This routine should not be applied to un-planned expressions */
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index 2470493708a0fd4bf2049eb0704229eeff11b4dd..84564dde73456153c8308994288c4a0d7585b451 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -12,6 +12,12 @@
  * this allows
  *		WHERE ctid IN (tid1, tid2, ...)
  *
+ * We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
+ * which amount to "CTID = run-time-determined-TID".  These could in
+ * theory be translated to a simple comparison of CTID to the result of
+ * a function, but in practice it works better to keep the special node
+ * representation all the way through to execution.
+ *
  * There is currently no special support for joins involving CTID; in
  * particular nothing corresponding to best_inner_indexscan().	Since it's
  * not very useful to store TIDs of one table in another table, there
@@ -24,7 +30,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.29 2007/01/05 22:19:31 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/tidpath.c,v 1.30 2007/06/11 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -174,6 +180,12 @@ TidQualFromExpr(Node *expr, int varno)
 		if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
 			rlst = list_make1(expr);
 	}
+	else if (expr && IsA(expr, CurrentOfExpr))
+	{
+		/* another base case: check for CURRENT OF on this rel */
+		if (((CurrentOfExpr *) expr)->cvarno == varno)
+			rlst = list_make1(expr);
+	}
 	else if (and_clause(expr))
 	{
 		foreach(l, ((BoolExpr *) expr)->args)
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 90a49983ac63798a334993a48031f71cf353eb8d..055b47beec7d6791aa055749eafe65f4299ccc0b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.135 2007/04/30 00:16:43 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.136 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -618,6 +618,15 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 			var->varnoold += context->rtoffset;
 		return (Node *) var;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+		Assert(cexpr->cvarno != INNER);
+		Assert(cexpr->cvarno != OUTER);
+		cexpr->cvarno += context->rtoffset;
+		return (Node *) cexpr;
+	}
 	/*
 	 * Since we update opcode info in-place, this part could possibly
 	 * scribble on the planner's input data structures, but it's OK.
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 2b273f738a8bf31152cbdf9467886a33428808e4..5e80dc1559a581aba8e7da0c28558c923a16e7db 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -22,7 +22,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.141 2007/04/21 05:56:41 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.142 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1132,6 +1132,14 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context)
 		}
 		return (Node *) var;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+		if (cexpr->cvarno == context->parent_relid)
+			cexpr->cvarno = context->child_relid;
+		return (Node *) cexpr;
+	}
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 00f5f6e2c8175fb067bc77058e9470ec29a8d7c1..2cf0ffd28b05e98f77e0f0a8e0b0190390acf37b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.245 2007/06/05 21:31:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.246 2007/06/11 01:16:23 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -3407,6 +3407,7 @@ expression_tree_walker(Node *node,
 		case T_CoerceToDomainValue:
 		case T_CaseTestExpr:
 		case T_SetToDefault:
+		case T_CurrentOfExpr:
 		case T_RangeTblRef:
 		case T_OuterJoinInfo:
 			/* primitive node types with no expression subnodes */
@@ -3873,6 +3874,7 @@ expression_tree_mutator(Node *node,
 		case T_CoerceToDomainValue:
 		case T_CaseTestExpr:
 		case T_SetToDefault:
+		case T_CurrentOfExpr:
 		case T_RangeTblRef:
 		case T_OuterJoinInfo:
 			return (Node *) copyObject(node);
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 13702b7d4656155712e315545fa7a69b475d7dc9..c501c8279228b4974d92d733624a2a81d014cb1b 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.69 2007/01/05 22:19:33 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.70 2007/06/11 01:16:23 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -111,6 +111,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
 			context->varnos = bms_add_member(context->varnos, var->varno);
 		return false;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+		if (context->sublevels_up == 0)
+			context->varnos = bms_add_member(context->varnos, cexpr->cvarno);
+		return false;
+	}
 	if (IsA(node, Query))
 	{
 		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
@@ -217,6 +225,8 @@ contain_var_clause_walker(Node *node, void *context)
 			return true;		/* abort the tree traversal and return true */
 		return false;
 	}
+	if (IsA(node, CurrentOfExpr))
+		return true;
 	return expression_tree_walker(node, contain_var_clause_walker, context);
 }
 
@@ -249,6 +259,13 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up)
 	{
 		if (((Var *) node)->varlevelsup == *sublevels_up)
 			return true;		/* abort tree traversal and return true */
+		return false;
+	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		if (*sublevels_up == 0)
+			return true;
+		return false;
 	}
 	if (IsA(node, Query))
 	{
@@ -376,6 +393,29 @@ find_minimum_var_level_walker(Node *node,
 			}
 		}
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		int			varlevelsup = 0;
+
+		/* convert levelsup to frame of reference of original query */
+		varlevelsup -= context->sublevels_up;
+		/* ignore local vars of subqueries */
+		if (varlevelsup >= 0)
+		{
+			if (context->min_varlevel < 0 ||
+				context->min_varlevel > varlevelsup)
+			{
+				context->min_varlevel = varlevelsup;
+
+				/*
+				 * As soon as we find a local variable, we can abort the tree
+				 * traversal, since min_varlevel is then certainly 0.
+				 */
+				if (varlevelsup == 0)
+					return true;
+			}
+		}
+	}
 
 	/*
 	 * An Aggref must be treated like a Var of its level.  Normally we'd get
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 68475387be367779f91683b0588779bcebd0d9f0..3ac6f000d857a00a45b00bbf2f897708dfdb362a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.363 2007/04/27 22:05:48 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.364 2007/06/11 01:16:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3178,12 +3178,12 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 				 errmsg("DECLARE CURSOR cannot specify INTO")));
 
-	/* Implementation restriction (might go away someday) */
-	if (result->rowMarks != NIL)
+	/* FOR UPDATE and WITH HOLD are not compatible */
+	if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			  errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
-				 errdetail("Cursors must be READ ONLY.")));
+			  errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
+				 errdetail("Holdable cursors must be READ ONLY.")));
 
 	/* We won't need the raw querytree any more */
 	stmt->query = NULL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8884da228921cad401ebb63ffb634f546bdeb1e4..b50be6bd739f7c32a7b957d94124d5604627bb42 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.591 2007/04/27 22:05:48 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.592 2007/06/11 01:16:25 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -296,7 +296,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 %type <node>	TableElement ConstraintElem TableFuncElement
 %type <node>	columnDef
 %type <defelt>	def_elem old_aggr_elem
-%type <node>	def_arg columnElem where_clause
+%type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr func_expr AexprConst indirection_el
 				columnref in_expr having_clause func_table array_expr
 %type <list>	row type_list array_expr_list
@@ -377,8 +377,8 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
 	COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS
 	CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB
-	CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
-	CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CREATEROLE CREATEUSER CROSS CSV CURRENT_P CURRENT_DATE CURRENT_ROLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
 	DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS
@@ -5715,7 +5715,7 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: DELETE_P FROM relation_expr_opt_alias
-			using_clause where_clause returning_clause
+			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
 					n->relation = $3;
@@ -5771,7 +5771,7 @@ opt_nowait:	NOWAIT							{ $$ = TRUE; }
 UpdateStmt: UPDATE relation_expr_opt_alias
 			SET set_clause_list
 			from_clause
-			where_clause
+			where_or_current_clause
 			returning_clause
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
@@ -6562,6 +6562,18 @@ where_clause:
 			| /*EMPTY*/								{ $$ = NULL; }
 		;
 
+/* variant for UPDATE and DELETE */
+where_or_current_clause:
+			WHERE a_expr							{ $$ = $2; }
+			| WHERE CURRENT_P OF name
+				{
+					CurrentOfExpr *n = makeNode(CurrentOfExpr);
+					n->cursor_name = $4;
+					$$ = (Node *) n;
+				}
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
 
 TableFuncElementList:
 			TableFuncElement
@@ -8818,6 +8830,7 @@ unreserved_keyword:
 			| CREATEROLE
 			| CREATEUSER
 			| CSV
+			| CURRENT_P
 			| CURSOR
 			| CYCLE
 			| DATABASE
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 5c8ef10a214a4646a61a71f94c61bd3fd06700b2..b48a0c79583d8a0adc45587d21634060f7a123be 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.187 2007/04/26 16:13:12 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.188 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"createuser", CREATEUSER},
 	{"cross", CROSS},
 	{"csv", CSV},
+	{"current", CURRENT_P},
 	{"current_date", CURRENT_DATE},
 	{"current_role", CURRENT_ROLE},
 	{"current_time", CURRENT_TIME},
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 45107e43acea9abb4d4e1f8552211a80a92e9979..6601bfe40ee4d8a5df0fd1b1f4cca07c895a8c93 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.218 2007/06/05 21:31:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.219 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -243,6 +243,21 @@ transformExpr(ParseState *pstate, Node *expr)
 			result = transformBooleanTest(pstate, (BooleanTest *) expr);
 			break;
 
+		case T_CurrentOfExpr:
+			{
+				CurrentOfExpr *c = (CurrentOfExpr *) expr;
+				int		sublevels_up;
+
+				/* CURRENT OF can only appear at top level of UPDATE/DELETE */
+				Assert(pstate->p_target_rangetblentry != NULL);
+				c->cvarno = RTERangeTablePosn(pstate,
+											  pstate->p_target_rangetblentry,
+											  &sublevels_up);
+				Assert(sublevels_up == 0);
+				result = expr;
+				break;
+			}
+
 			/*********************************************
 			 * Quietly accept node types that may be presented when we are
 			 * called on an already-transformed tree.
@@ -1863,6 +1878,9 @@ exprType(Node *expr)
 		case T_SetToDefault:
 			type = ((SetToDefault *) expr)->typeId;
 			break;
+		case T_CurrentOfExpr:
+			type = BOOLOID;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 8ea9ac103c65fdf655d72807a4556128f806261e..19b566389051d899647cda96970147caddec805c 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.103 2007/01/05 22:19:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.104 2007/06/11 01:16:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -151,6 +151,14 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 		}
 		return false;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+		if (context->sublevels_up == 0)
+			cexpr->cvarno += context->offset;
+		return false;
+	}
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) node;
@@ -302,6 +310,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 		}
 		return false;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+		if (context->sublevels_up == 0 &&
+			cexpr->cvarno == context->rt_index)
+			cexpr->cvarno = context->new_index;
+		return false;
+	}
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) node;
@@ -466,6 +483,13 @@ IncrementVarSublevelsUp_walker(Node *node,
 			var->varlevelsup += context->delta_sublevels_up;
 		return false;			/* done here */
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		/* this should not happen */
+		if (context->min_sublevels_up == 0)
+			elog(ERROR, "cannot push down CurrentOfExpr");
+		return false;
+	}
 	if (IsA(node, Aggref))
 	{
 		Aggref	   *agg = (Aggref *) node;
@@ -536,6 +560,15 @@ rangeTableEntry_used_walker(Node *node,
 			return true;
 		return false;
 	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+		if (context->sublevels_up == 0 &&
+			cexpr->cvarno == context->rt_index)
+			return true;
+		return false;
+	}
 	if (IsA(node, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) node;
@@ -932,8 +965,27 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 		}
 		/* otherwise fall through to copy the var normally */
 	}
+	else if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+		int			this_varno = (int) cexpr->cvarno;
 
-	if (IsA(node, Query))
+		if (this_varno == context->target_varno &&
+			context->sublevels_up == 0)
+		{
+			/*
+			 * We get here if a WHERE CURRENT OF expression turns out to
+			 * apply to a view.  Someday we might be able to translate
+			 * the expression to apply to an underlying table of the view,
+			 * but right now it's not implemented.
+			 */
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("WHERE CURRENT OF on a view is not implemented")));
+		}
+		/* otherwise fall through to copy the expr normally */
+	}
+	else if (IsA(node, Query))
 	{
 		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
 		Query	   *newnode;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 78be35ef36da901bf22009750b2333dde13f9e79..c6f6b88248756b4bf6a95cc454b13c2ac7f94f2a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.259 2007/06/05 21:31:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.260 2007/06/11 01:16:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3086,6 +3086,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 		case T_Param:
 		case T_CoerceToDomainValue:
 		case T_SetToDefault:
+		case T_CurrentOfExpr:
 			/* single words: always simple */
 			return true;
 
@@ -4134,6 +4135,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			appendStringInfo(buf, "DEFAULT");
 			break;
 
+		case T_CurrentOfExpr:
+			appendStringInfo(buf, "CURRENT OF %s",
+					quote_identifier(((CurrentOfExpr *) node)->cursor_name));
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index f9d8d107b36d4eae81158777578465bce37926d1..408519c1e35704da3e94353098ff4621e934aae2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.139 2007/02/27 01:11:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.140 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -70,6 +70,12 @@ extern bool ExecSupportsMarkRestore(NodeTag plantype);
 extern bool ExecSupportsBackwardScan(Plan *node);
 extern bool ExecMayReturnRawTuples(PlanState *node);
 
+/*
+ * prototypes from functions in execCurrent.c
+ */
+extern bool execCurrentOf(char *cursor_name, Oid table_oid,
+						  ItemPointer current_tid);
+
 /*
  * prototypes from functions in execGrouping.c
  */
@@ -135,6 +141,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate);
 extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
 			 ItemPointer tid, TransactionId priorXmax, CommandId curCid);
+extern PlanState *ExecGetActivePlanTree(QueryDesc *queryDesc);
 extern DestReceiver *CreateIntoRelDestReceiver(void);
 
 /*
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 31186930d20c3381b4edfbaa152fb03c8f40193e..c54a1a4522c41f7041ca2e6450e1ceaaff4a56ae 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.200 2007/06/05 21:31:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.201 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -139,6 +139,7 @@ typedef enum NodeTag
 	T_CoerceToDomain,
 	T_CoerceToDomainValue,
 	T_SetToDefault,
+	T_CurrentOfExpr,
 	T_TargetEntry,
 	T_RangeTblRef,
 	T_JoinExpr,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index a567a8e26d49f5edd2a7a94caedb3ab914a69c5e..9a3e09b77ec6c3dc3cb991b945e91ae8dbe5de09 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.130 2007/06/05 21:31:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.131 2007/06/11 01:16:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -915,6 +915,21 @@ typedef struct SetToDefault
 	int32		typeMod;		/* typemod for substituted value */
 } SetToDefault;
 
+/*
+ * Node representing [WHERE] CURRENT OF cursor_name
+ *
+ * CURRENT OF is a bit like a Var, in that it carries the rangetable index
+ * of the target relation being constrained; this aids placing the expression
+ * correctly during planning.  We can assume however that its "levelsup" is
+ * always zero, due to the syntactic constraints on where it can appear.
+ */
+typedef struct CurrentOfExpr
+{
+	Expr		xpr;
+	Index		cvarno;			/* RT index of target relation */
+	char	   *cursor_name;	/* name of referenced cursor */
+} CurrentOfExpr;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
index 9e618dbc5fb8d7acf86c4fa884be241a8a155352..3638664b1bbc1cc3aca427bb12a6443f4ce6c883 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -899,3 +899,176 @@ SELECT name FROM pg_cursors ORDER BY 1;
 (0 rows)
 
 COMMIT;
+--
+-- Tests for updatable cursors
+--
+CREATE TEMP TABLE uctest(f1 int, f2 text);
+INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three');
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  2 | two
+  3 | three
+(3 rows)
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 2 FROM c1;
+ f1 | f2  
+----+-----
+  1 | one
+  2 | two
+(2 rows)
+
+DELETE FROM uctest WHERE CURRENT OF c1;
+-- should show deletion
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  3 | three
+(2 rows)
+
+-- cursor did not move
+FETCH ALL FROM c1;
+ f1 |  f2   
+----+-------
+  3 | three
+(1 row)
+
+-- cursor is insensitive
+MOVE BACKWARD ALL IN c1;
+FETCH ALL FROM c1;
+ f1 |  f2   
+----+-------
+  1 | one
+  2 | two
+  3 | three
+(3 rows)
+
+COMMIT;
+-- should still see deletion
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  1 | one
+  3 | three
+(2 rows)
+
+-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+ f1 | f2  
+----+-----
+  1 | one
+(1 row)
+
+UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  3 | three
+  8 | one
+(2 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1 |  f2   
+----+-------
+  3 | three
+  8 | one
+(2 rows)
+
+-- Check inheritance cases
+CREATE TEMP TABLE ucchild () inherits (uctest);
+INSERT INTO ucchild values(100, 'hundred');
+SELECT * FROM uctest;
+ f1  |   f2    
+-----+---------
+   3 | three
+   8 | one
+ 100 | hundred
+(3 rows)
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 1 FROM c1;
+ f1 |  f2   
+----+-------
+  3 | three
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2  
+----+-----
+  8 | one
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1  |   f2    
+-----+---------
+ 100 | hundred
+(1 row)
+
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+ f1 | f2 
+----+----
+(0 rows)
+
+COMMIT;
+SELECT * FROM uctest;
+ f1  |   f2    
+-----+---------
+  13 | three
+  18 | one
+ 110 | hundred
+(3 rows)
+
+-- Check various error cases
+DELETE FROM uctest WHERE CURRENT OF c1;  -- fail, no such cursor
+ERROR:  cursor "c1" does not exist
+DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF cx;  -- fail, can't use held cursor
+ERROR:  cursor "cx" is held from a previous transaction
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor on wrong table
+ERROR:  cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
+DELETE FROM tenk1 WHERE CURRENT OF c;  -- fail, cursor is on a join
+ERROR:  cursor "c" is not a simply updatable scan of table "tenk1"
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor is on aggregation
+ERROR:  cursor "c" is not a simply updatable scan of table "uctest"
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row
+ERROR:  cursor "c1" is not positioned on a row
+ROLLBACK;
+-- WHERE CURRENT OF may someday work with views, but today is not that day.
+-- For now, just make sure it errors out cleanly.
+CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
+CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
+  DELETE FROM uctest WHERE f1 = OLD.f1;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM ucview;
+FETCH FROM c1;
+ f1 |  f2   
+----+-------
+ 13 | three
+(1 row)
+
+DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
+ERROR:  WHERE CURRENT OF on a view is not implemented
+ROLLBACK;
diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql
index 18f803b4391690e0bc5b8ef4e6a73f0f42cf2017..382a28c4e30bfa156de6f5c0a0d442c5851a56af 100644
--- a/src/test/regress/sql/portals.sql
+++ b/src/test/regress/sql/portals.sql
@@ -316,5 +316,85 @@ CLOSE ALL;
 SELECT name FROM pg_cursors ORDER BY 1;
 COMMIT;
 
+--
+-- Tests for updatable cursors
+--
+
+CREATE TEMP TABLE uctest(f1 int, f2 text);
+INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three');
+SELECT * FROM uctest;
+
+-- Check DELETE WHERE CURRENT
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 2 FROM c1;
+DELETE FROM uctest WHERE CURRENT OF c1;
+-- should show deletion
+SELECT * FROM uctest;
+-- cursor did not move
+FETCH ALL FROM c1;
+-- cursor is insensitive
+MOVE BACKWARD ALL IN c1;
+FETCH ALL FROM c1;
+COMMIT;
+-- should still see deletion
+SELECT * FROM uctest;
+
+-- Check UPDATE WHERE CURRENT; this time use FOR UPDATE
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE;
+FETCH c1;
+UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1;
+SELECT * FROM uctest;
+COMMIT;
+SELECT * FROM uctest;
+
+-- Check inheritance cases
+CREATE TEMP TABLE ucchild () inherits (uctest);
+INSERT INTO ucchild values(100, 'hundred');
+SELECT * FROM uctest;
+
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1;
+FETCH 1 FROM c1;
+COMMIT;
+SELECT * FROM uctest;
+
+-- Check various error cases
 
+DELETE FROM uctest WHERE CURRENT OF c1;  -- fail, no such cursor
+DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF cx;  -- fail, can't use held cursor
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk2;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor on wrong table
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1);
+DELETE FROM tenk1 WHERE CURRENT OF c;  -- fail, cursor is on a join
+ROLLBACK;
+BEGIN;
+DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1;
+DELETE FROM uctest WHERE CURRENT OF c;  -- fail, cursor is on aggregation
+ROLLBACK;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM uctest;
+DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row
+ROLLBACK;
 
+-- WHERE CURRENT OF may someday work with views, but today is not that day.
+-- For now, just make sure it errors out cleanly.
+CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
+CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
+  DELETE FROM uctest WHERE f1 = OLD.f1;
+BEGIN;
+DECLARE c1 CURSOR FOR SELECT * FROM ucview;
+FETCH FROM c1;
+DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported
+ROLLBACK;