summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2007-06-11 01:16:30 +0000
committerTom Lane2007-06-11 01:16:30 +0000
commit6808f1b1de0ebcd4af558ba84c3226b2027f55ea (patch)
treeebd12580d3aaca6ec79b5d99563a1eff02451e88
parent85d72f05167b87bc44464b2eabea8538f1fd1e45 (diff)
Support UPDATE/DELETE WHERE CURRENT OF cursor_name, per SQL standard.
Along the way, allow FOR UPDATE in non-WITH-HOLD cursors; there may once have been a reason to disallow that, but it seems to work now, and it's really rather necessary if you want to select a row via a cursor and then update it in a concurrent-safe fashion. Original patch by Arul Shaji, rather heavily editorialized by Tom Lane.
-rw-r--r--doc/src/sgml/ref/declare.sgml149
-rw-r--r--doc/src/sgml/ref/delete.sgml32
-rw-r--r--doc/src/sgml/ref/update.sgml27
-rw-r--r--src/backend/executor/Makefile4
-rw-r--r--src/backend/executor/execCurrent.c185
-rw-r--r--src/backend/executor/execMain.c20
-rw-r--r--src/backend/executor/execQual.c43
-rw-r--r--src/backend/executor/nodeTidscan.c25
-rw-r--r--src/backend/nodes/copyfuncs.c19
-rw-r--r--src/backend/nodes/equalfuncs.c14
-rw-r--r--src/backend/nodes/outfuncs.c14
-rw-r--r--src/backend/nodes/readfuncs.c18
-rw-r--r--src/backend/optimizer/path/clausesel.c12
-rw-r--r--src/backend/optimizer/path/costsize.c20
-rw-r--r--src/backend/optimizer/path/tidpath.c14
-rw-r--r--src/backend/optimizer/plan/setrefs.c11
-rw-r--r--src/backend/optimizer/prep/prepunion.c10
-rw-r--r--src/backend/optimizer/util/clauses.c4
-rw-r--r--src/backend/optimizer/util/var.c42
-rw-r--r--src/backend/parser/analyze.c10
-rw-r--r--src/backend/parser/gram.y25
-rw-r--r--src/backend/parser/keywords.c3
-rw-r--r--src/backend/parser/parse_expr.c20
-rw-r--r--src/backend/rewrite/rewriteManip.c56
-rw-r--r--src/backend/utils/adt/ruleutils.c8
-rw-r--r--src/include/executor/executor.h9
-rw-r--r--src/include/nodes/nodes.h3
-rw-r--r--src/include/nodes/primnodes.h17
-rw-r--r--src/test/regress/expected/portals.out173
-rw-r--r--src/test/regress/sql/portals.sql80
30 files changed, 940 insertions, 127 deletions
diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml
index 4afe2d03b25..f823cf77bbe 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
@@ -233,6 +196,11 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
</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
the SQL standard. However, for compatibility with earlier
@@ -246,6 +214,23 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
</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>
server does not implement an <command>OPEN</command> statement for
@@ -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 f57c56ecf7e..b5c7d97f524 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>
@@ -238,6 +252,14 @@ DELETE FROM films;
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>
<refsect1>
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index df0bec9a1d6..2c6a480c4a5 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>
@@ -161,6 +161,20 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
</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>
<para>
@@ -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 55256d9e469..cb4ab9dc31a 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 00000000000..ce95d58b81b
--- /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 82b17b19f01..dfd1a84ab77 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 fd54b89f0a5..5549142e703 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 9b6724537e7..986ff1f3f19 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 39027b1dbc8..c868ff574d9 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 $
*
*-------------------------------------------------------------------------
*/
@@ -1300,6 +1300,20 @@ _copySetToDefault(SetToDefault *from)
}
/*
+ * _copyCurrentOfExpr
+ */
+static CurrentOfExpr *
+_copyCurrentOfExpr(CurrentOfExpr *from)
+{
+ CurrentOfExpr *newnode = makeNode(CurrentOfExpr);
+
+ COPY_SCALAR_FIELD(cvarno);
+ COPY_STRING_FIELD(cursor_name);
+
+ return newnode;
+}
+
+/*
* _copyTargetEntry
*/
static TargetEntry *
@@ -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 8a0957c117e..04072c7a654 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 $
*
*-------------------------------------------------------------------------
*/
@@ -599,6 +599,15 @@ _equalSetToDefault(SetToDefault *a, SetToDefault *b)
}
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)
{
COMPARE_NODE_FIELD(expr);
@@ -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 5de540642f4..869905f0cc5 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*
@@ -1059,6 +1059,15 @@ _outSetToDefault(StringInfo str, SetToDefault *node)
}
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)
{
WRITE_NODE_TYPE("TARGETENTRY");
@@ -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 86c9e911a7c..e91a6e5b501 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
@@ -874,6 +874,20 @@ _readSetToDefault(void)
}
/*
+ * _readCurrentOfExpr
+ */
+static CurrentOfExpr *
+_readCurrentOfExpr(void)
+{
+ READ_LOCALS(CurrentOfExpr);
+
+ READ_UINT_FIELD(cvarno);
+ READ_STRING_FIELD(cursor_name);
+
+ READ_DONE();
+}
+
+/*
* _readTargetEntry
*/
static TargetEntry *
@@ -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 b8bbc29c505..4b48ae1e260 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 a4d03e9f8f8..f76c778998b 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 2470493708a..84564dde734 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 90a49983ac6..055b47beec7 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 2b273f738a8..5e80dc1559a 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 00f5f6e2c81..2cf0ffd28b0 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 13702b7d465..c501c827922 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 68475387be3..3ac6f000d85 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 8884da22892..b50be6bd739 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 5c8ef10a214..b48a0c79583 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 45107e43ace..6601bfe40ee 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 8ea9ac103c6..19b56638905 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 78be35ef36d..c6f6b882487 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 f9d8d107b36..408519c1e35 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 $
*
*-------------------------------------------------------------------------
*/
@@ -71,6 +71,12 @@ 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
*/
extern bool execTuplesMatch(TupleTableSlot *slot1,
@@ -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 31186930d20..c54a1a4522c 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 a567a8e26d4..9a3e09b77ec 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 9e618dbc5fb..3638664b1bb 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 18f803b4391..382a28c4e30 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;