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.
This commit is contained in:
Tom Lane 2007-06-11 01:16:30 +00:00
parent 85d72f0516
commit 6808f1b1de
30 changed files with 940 additions and 127 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 \

View File

@ -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;
}

View File

@ -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)

View File

@ -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;

View File

@ -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");
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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))

View File

@ -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 */

View File

@ -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 */

View File

@ -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)

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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},

View File

@ -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 */

View File

@ -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;

View File

@ -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;

View File

@ -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);
/*

View File

@ -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,

View File

@ -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)

View File

@ -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;

View File

@ -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;