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:
parent
85d72f0516
commit
6808f1b1de
@ -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
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ PostgreSQL documentation
|
|||||||
<synopsis>
|
<synopsis>
|
||||||
DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ]
|
DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITIVE ] [ [ NO ] SCROLL ]
|
||||||
CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="parameter">query</replaceable>
|
CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable class="parameter">query</replaceable>
|
||||||
[ FOR { READ ONLY | UPDATE [ OF <replaceable class="parameter">column</replaceable> [, ...] ] } ]
|
|
||||||
</synopsis>
|
</synopsis>
|
||||||
</refsynopsisdiv>
|
</refsynopsisdiv>
|
||||||
|
|
||||||
@ -37,50 +36,10 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
|
|||||||
<para>
|
<para>
|
||||||
<command>DECLARE</command> allows a user to create cursors, which
|
<command>DECLARE</command> allows a user to create cursors, which
|
||||||
can be used to retrieve
|
can be used to retrieve
|
||||||
a small number of rows at a time out of a larger query. Cursors can
|
a small number of rows at a time out of a larger query.
|
||||||
return data either in text or in binary format using
|
After the cursor is created, rows are fetched from it using
|
||||||
<xref linkend="sql-fetch" endterm="sql-fetch-title">.
|
<xref linkend="sql-fetch" endterm="sql-fetch-title">.
|
||||||
</para>
|
</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 — any cursor can be treated as either text or binary.
|
|
||||||
</para>
|
|
||||||
</note>
|
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
@ -110,10 +69,10 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Indicates that data retrieved from the cursor should be
|
Indicates that data retrieved from the cursor should be
|
||||||
unaffected by updates to the tables underlying the cursor while
|
unaffected by updates to the table(s) underlying the cursor that occur
|
||||||
the cursor exists. In <productname>PostgreSQL</productname>,
|
after the cursor is created. In <productname>PostgreSQL</productname>,
|
||||||
all cursors are insensitive; this key word currently has no
|
this is the default behavior; so this key word has no
|
||||||
effect and is present for compatibility with the SQL standard.
|
effect and is only accepted for compatibility with the SQL standard.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -163,34 +122,6 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
</variablelist>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -203,6 +134,38 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
|
|||||||
<refsect1 id="sql-declare-notes">
|
<refsect1 id="sql-declare-notes">
|
||||||
<title id="sql-declare-notes-title">Notes</title>
|
<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 — any cursor can be treated as either text or binary.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Unless <literal>WITH HOLD</literal> is specified, the cursor
|
Unless <literal>WITH HOLD</literal> is specified, the cursor
|
||||||
created by this command can only be used within the current
|
created by this command can only be used within the current
|
||||||
@ -232,6 +195,11 @@ DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ INSENSITI
|
|||||||
transactions.
|
transactions.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>WITH HOLD</literal> may not be specified when the query
|
||||||
|
includes <literal>FOR UPDATE</> or <literal>FOR SHARE</>.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The <literal>SCROLL</> option should be specified when defining a
|
The <literal>SCROLL</> option should be specified when defining a
|
||||||
cursor that will be used to fetch backwards. This is required by
|
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.
|
specified, then backward fetches are disallowed in any case.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
The SQL standard only makes provisions for cursors in embedded
|
The SQL standard only makes provisions for cursors in embedded
|
||||||
<acronym>SQL</acronym>. The <productname>PostgreSQL</productname>
|
<acronym>SQL</acronym>. The <productname>PostgreSQL</productname>
|
||||||
@ -280,14 +265,16 @@ DECLARE liahona CURSOR FOR SELECT * FROM films;
|
|||||||
<title>Compatibility</title>
|
<title>Compatibility</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The SQL standard allows cursors only in embedded
|
The SQL standard specifies that by default, cursors are sensitive to
|
||||||
<acronym>SQL</acronym> and in modules. <productname>PostgreSQL</>
|
concurrent updates of the underlying data. In
|
||||||
permits cursors to be used interactively.
|
<productname>PostgreSQL</productname>, cursors are insensitive by default,
|
||||||
|
and can be made sensitive by specifying <literal>FOR UPDATE</>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The SQL standard allows cursors to update table data. All
|
The SQL standard allows cursors only in embedded
|
||||||
<productname>PostgreSQL</> cursors are read only.
|
<acronym>SQL</acronym> and in modules. <productname>PostgreSQL</>
|
||||||
|
permits cursors to be used interactively.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -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
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ PostgreSQL documentation
|
|||||||
<synopsis>
|
<synopsis>
|
||||||
DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
|
DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
|
||||||
[ USING <replaceable class="PARAMETER">usinglist</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> ] [, ...] ]
|
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ AS <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
|
||||||
</synopsis>
|
</synopsis>
|
||||||
</refsynopsisdiv>
|
</refsynopsisdiv>
|
||||||
@ -134,9 +134,23 @@ DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ]
|
|||||||
<term><replaceable class="parameter">condition</replaceable></term>
|
<term><replaceable class="parameter">condition</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
An expression returning a value of type
|
An expression that returns a value of type <type>boolean</type>.
|
||||||
<type>boolean</type>, which determines the rows that are to be
|
Only rows for which this expression returns <literal>true</>
|
||||||
deleted.
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -236,6 +250,14 @@ DELETE FROM films;
|
|||||||
Delete completed tasks, returning full details of the deleted rows:
|
Delete completed tasks, returning full details of the deleted rows:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
DELETE FROM tasks WHERE status = 'DONE' RETURNING *;
|
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>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
@ -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
|
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 } |
|
SET { <replaceable class="PARAMETER">column</replaceable> = { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } |
|
||||||
( <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> ]
|
[ 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> ] [, ...] ]
|
[ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ AS <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
|
||||||
</synopsis>
|
</synopsis>
|
||||||
</refsynopsisdiv>
|
</refsynopsisdiv>
|
||||||
@ -160,6 +160,20 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><replaceable class="PARAMETER">output_expression</replaceable></term>
|
<term><replaceable class="PARAMETER">output_expression</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -309,6 +323,15 @@ UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
|
|||||||
COMMIT;
|
COMMIT;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</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>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# Makefile for executor
|
# Makefile for executor
|
||||||
#
|
#
|
||||||
# IDENTIFICATION
|
# 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 = ../../..
|
top_builddir = ../../..
|
||||||
include $(top_builddir)/src/Makefile.global
|
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 \
|
execProcnode.o execQual.o execScan.o execTuples.o \
|
||||||
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
|
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
|
||||||
nodeBitmapAnd.o nodeBitmapOr.o \
|
nodeBitmapAnd.o nodeBitmapOr.o \
|
||||||
|
185
src/backend/executor/execCurrent.c
Normal file
185
src/backend/executor/execCurrent.c
Normal 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;
|
||||||
|
}
|
@ -26,7 +26,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
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)
|
* Support for SELECT INTO (a/k/a CREATE TABLE AS)
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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,
|
static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
|
||||||
ExprContext *econtext,
|
ExprContext *econtext,
|
||||||
bool *isNull, ExprDoneCond *isDone);
|
bool *isNull, ExprDoneCond *isDone);
|
||||||
|
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
|
||||||
|
bool *isNull, ExprDoneCond *isDone);
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -3618,6 +3620,41 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
|
|||||||
astate->amstate);
|
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
|
* ExecEvalExprSwitchContext
|
||||||
@ -4266,6 +4303,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
|
|||||||
state = (ExprState *) cstate;
|
state = (ExprState *) cstate;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
state = (ExprState *) makeNode(ExprState);
|
||||||
|
state->evalfunc = ExecEvalCurrentOfExpr;
|
||||||
|
break;
|
||||||
case T_TargetEntry:
|
case T_TargetEntry:
|
||||||
{
|
{
|
||||||
TargetEntry *tle = (TargetEntry *) node;
|
TargetEntry *tle = (TargetEntry *) node;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* 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
|
* are simple OpExprs or CurrentOfExprs. If there are any
|
||||||
* enlarge the array.
|
* ScalarArrayOpExprs, we may have to enlarge the array.
|
||||||
*/
|
*/
|
||||||
numAllocTids = list_length(evalList);
|
numAllocTids = list_length(evalList);
|
||||||
tidList = (ItemPointerData *)
|
tidList = (ItemPointerData *)
|
||||||
@ -148,6 +148,25 @@ TidListCreate(TidScanState *tidstate)
|
|||||||
pfree(ipdatums);
|
pfree(ipdatums);
|
||||||
pfree(ipnulls);
|
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
|
else
|
||||||
elog(ERROR, "could not identify CTID expression");
|
elog(ERROR, "could not identify CTID expression");
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
return newnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* _copyCurrentOfExpr
|
||||||
|
*/
|
||||||
|
static CurrentOfExpr *
|
||||||
|
_copyCurrentOfExpr(CurrentOfExpr *from)
|
||||||
|
{
|
||||||
|
CurrentOfExpr *newnode = makeNode(CurrentOfExpr);
|
||||||
|
|
||||||
|
COPY_SCALAR_FIELD(cvarno);
|
||||||
|
COPY_STRING_FIELD(cursor_name);
|
||||||
|
|
||||||
|
return newnode;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* _copyTargetEntry
|
* _copyTargetEntry
|
||||||
*/
|
*/
|
||||||
@ -3177,6 +3191,9 @@ copyObject(void *from)
|
|||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
retval = _copySetToDefault(from);
|
retval = _copySetToDefault(from);
|
||||||
break;
|
break;
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
retval = _copyCurrentOfExpr(from);
|
||||||
|
break;
|
||||||
case T_TargetEntry:
|
case T_TargetEntry:
|
||||||
retval = _copyTargetEntry(from);
|
retval = _copyTargetEntry(from);
|
||||||
break;
|
break;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
_equalCurrentOfExpr(CurrentOfExpr *a, CurrentOfExpr *b)
|
||||||
|
{
|
||||||
|
COMPARE_SCALAR_FIELD(cvarno);
|
||||||
|
COMPARE_STRING_FIELD(cursor_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
_equalTargetEntry(TargetEntry *a, TargetEntry *b)
|
_equalTargetEntry(TargetEntry *a, TargetEntry *b)
|
||||||
{
|
{
|
||||||
@ -2124,6 +2133,9 @@ equal(void *a, void *b)
|
|||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
retval = _equalSetToDefault(a, b);
|
retval = _equalSetToDefault(a, b);
|
||||||
break;
|
break;
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
retval = _equalCurrentOfExpr(a, b);
|
||||||
|
break;
|
||||||
case T_TargetEntry:
|
case T_TargetEntry:
|
||||||
retval = _equalTargetEntry(a, b);
|
retval = _equalTargetEntry(a, b);
|
||||||
break;
|
break;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* NOTES
|
||||||
* Every node type that can appear in stored rules' parsetrees *must*
|
* Every node type that can appear in stored rules' parsetrees *must*
|
||||||
@ -1058,6 +1058,15 @@ _outSetToDefault(StringInfo str, SetToDefault *node)
|
|||||||
WRITE_INT_FIELD(typeMod);
|
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
|
static void
|
||||||
_outTargetEntry(StringInfo str, TargetEntry *node)
|
_outTargetEntry(StringInfo str, TargetEntry *node)
|
||||||
{
|
{
|
||||||
@ -2229,6 +2238,9 @@ _outNode(StringInfo str, void *obj)
|
|||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
_outSetToDefault(str, obj);
|
_outSetToDefault(str, obj);
|
||||||
break;
|
break;
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
_outCurrentOfExpr(str, obj);
|
||||||
|
break;
|
||||||
case T_TargetEntry:
|
case T_TargetEntry:
|
||||||
_outTargetEntry(str, obj);
|
_outTargetEntry(str, obj);
|
||||||
break;
|
break;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* NOTES
|
||||||
* Path and Plan nodes do not have any readfuncs support, because we
|
* Path and Plan nodes do not have any readfuncs support, because we
|
||||||
@ -873,6 +873,20 @@ _readSetToDefault(void)
|
|||||||
READ_DONE();
|
READ_DONE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* _readCurrentOfExpr
|
||||||
|
*/
|
||||||
|
static CurrentOfExpr *
|
||||||
|
_readCurrentOfExpr(void)
|
||||||
|
{
|
||||||
|
READ_LOCALS(CurrentOfExpr);
|
||||||
|
|
||||||
|
READ_UINT_FIELD(cvarno);
|
||||||
|
READ_STRING_FIELD(cursor_name);
|
||||||
|
|
||||||
|
READ_DONE();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* _readTargetEntry
|
* _readTargetEntry
|
||||||
*/
|
*/
|
||||||
@ -1093,6 +1107,8 @@ parseNodeString(void)
|
|||||||
return_value = _readCoerceToDomainValue();
|
return_value = _readCoerceToDomainValue();
|
||||||
else if (MATCH("SETTODEFAULT", 12))
|
else if (MATCH("SETTODEFAULT", 12))
|
||||||
return_value = _readSetToDefault();
|
return_value = _readSetToDefault();
|
||||||
|
else if (MATCH("CURRENTOFEXPR", 13))
|
||||||
|
return_value = _readCurrentOfExpr();
|
||||||
else if (MATCH("TARGETENTRY", 11))
|
else if (MATCH("TARGETENTRY", 11))
|
||||||
return_value = _readTargetEntry();
|
return_value = _readTargetEntry();
|
||||||
else if (MATCH("RANGETBLREF", 11))
|
else if (MATCH("RANGETBLREF", 11))
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 "nodes/makefuncs.h"
|
||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
#include "optimizer/cost.h"
|
#include "optimizer/cost.h"
|
||||||
|
#include "optimizer/pathnode.h"
|
||||||
#include "optimizer/plancat.h"
|
#include "optimizer/plancat.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
#include "utils/fmgroids.h"
|
#include "utils/fmgroids.h"
|
||||||
@ -712,6 +713,15 @@ clause_selectivity(PlannerInfo *root,
|
|||||||
varRelid,
|
varRelid,
|
||||||
jointype);
|
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))
|
else if (IsA(clause, RelabelType))
|
||||||
{
|
{
|
||||||
/* Not sure this case is needed, but it can't hurt */
|
/* Not sure this case is needed, but it can't hurt */
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 startup_cost = 0;
|
||||||
Cost run_cost = 0;
|
Cost run_cost = 0;
|
||||||
Cost cpu_per_tuple;
|
Cost cpu_per_tuple;
|
||||||
|
QualCost tid_qual_cost;
|
||||||
int ntuples;
|
int ntuples;
|
||||||
ListCell *l;
|
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 */
|
/* disk costs --- assume each tuple on a different page */
|
||||||
run_cost += random_page_cost * ntuples;
|
run_cost += random_page_cost * ntuples;
|
||||||
|
|
||||||
/* CPU costs */
|
/* CPU costs */
|
||||||
startup_cost += baserel->baserestrictcost.startup;
|
startup_cost += baserel->baserestrictcost.startup +
|
||||||
cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
|
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;
|
run_cost += cpu_per_tuple * ntuples;
|
||||||
|
|
||||||
path->startup_cost = startup_cost;
|
path->startup_cost = startup_cost;
|
||||||
@ -1991,6 +2000,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
|
|||||||
cpu_operator_cost;
|
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))
|
else if (IsA(node, SubLink))
|
||||||
{
|
{
|
||||||
/* This routine should not be applied to un-planned expressions */
|
/* This routine should not be applied to un-planned expressions */
|
||||||
|
@ -12,6 +12,12 @@
|
|||||||
* this allows
|
* this allows
|
||||||
* WHERE ctid IN (tid1, tid2, ...)
|
* 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
|
* There is currently no special support for joins involving CTID; in
|
||||||
* particular nothing corresponding to best_inner_indexscan(). Since it's
|
* particular nothing corresponding to best_inner_indexscan(). Since it's
|
||||||
* not very useful to store TIDs of one table in another table, there
|
* not very useful to store TIDs of one table in another table, there
|
||||||
@ -24,7 +30,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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))
|
if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
|
||||||
rlst = list_make1(expr);
|
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))
|
else if (and_clause(expr))
|
||||||
{
|
{
|
||||||
foreach(l, ((BoolExpr *) expr)->args)
|
foreach(l, ((BoolExpr *) expr)->args)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
var->varnoold += context->rtoffset;
|
||||||
return (Node *) var;
|
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
|
* Since we update opcode info in-place, this part could possibly
|
||||||
* scribble on the planner's input data structures, but it's OK.
|
* scribble on the planner's input data structures, but it's OK.
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
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))
|
if (IsA(node, RangeTblRef))
|
||||||
{
|
{
|
||||||
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
|
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -3407,6 +3407,7 @@ expression_tree_walker(Node *node,
|
|||||||
case T_CoerceToDomainValue:
|
case T_CoerceToDomainValue:
|
||||||
case T_CaseTestExpr:
|
case T_CaseTestExpr:
|
||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
|
case T_CurrentOfExpr:
|
||||||
case T_RangeTblRef:
|
case T_RangeTblRef:
|
||||||
case T_OuterJoinInfo:
|
case T_OuterJoinInfo:
|
||||||
/* primitive node types with no expression subnodes */
|
/* primitive node types with no expression subnodes */
|
||||||
@ -3873,6 +3874,7 @@ expression_tree_mutator(Node *node,
|
|||||||
case T_CoerceToDomainValue:
|
case T_CoerceToDomainValue:
|
||||||
case T_CaseTestExpr:
|
case T_CaseTestExpr:
|
||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
|
case T_CurrentOfExpr:
|
||||||
case T_RangeTblRef:
|
case T_RangeTblRef:
|
||||||
case T_OuterJoinInfo:
|
case T_OuterJoinInfo:
|
||||||
return (Node *) copyObject(node);
|
return (Node *) copyObject(node);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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);
|
context->varnos = bms_add_member(context->varnos, var->varno);
|
||||||
return false;
|
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))
|
if (IsA(node, Query))
|
||||||
{
|
{
|
||||||
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
|
/* 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 true; /* abort the tree traversal and return true */
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (IsA(node, CurrentOfExpr))
|
||||||
|
return true;
|
||||||
return expression_tree_walker(node, contain_var_clause_walker, context);
|
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)
|
if (((Var *) node)->varlevelsup == *sublevels_up)
|
||||||
return true; /* abort tree traversal and return true */
|
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))
|
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
|
* An Aggref must be treated like a Var of its level. Normally we'd get
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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),
|
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
||||||
errmsg("DECLARE CURSOR cannot specify INTO")));
|
errmsg("DECLARE CURSOR cannot specify INTO")));
|
||||||
|
|
||||||
/* Implementation restriction (might go away someday) */
|
/* FOR UPDATE and WITH HOLD are not compatible */
|
||||||
if (result->rowMarks != NIL)
|
if (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
|
errmsg("DECLARE CURSOR WITH HOLD ... FOR UPDATE/SHARE is not supported"),
|
||||||
errdetail("Cursors must be READ ONLY.")));
|
errdetail("Holdable cursors must be READ ONLY.")));
|
||||||
|
|
||||||
/* We won't need the raw querytree any more */
|
/* We won't need the raw querytree any more */
|
||||||
stmt->query = NULL;
|
stmt->query = NULL;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* 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> TableElement ConstraintElem TableFuncElement
|
||||||
%type <node> columnDef
|
%type <node> columnDef
|
||||||
%type <defelt> def_elem old_aggr_elem
|
%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
|
a_expr b_expr c_expr func_expr AexprConst indirection_el
|
||||||
columnref in_expr having_clause func_table array_expr
|
columnref in_expr having_clause func_table array_expr
|
||||||
%type <list> row type_list array_expr_list
|
%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
|
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
|
||||||
COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS
|
COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS
|
||||||
CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB
|
CONTENT_P CONVERSION_P CONVERT COPY COST CREATE CREATEDB
|
||||||
CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
|
CREATEROLE CREATEUSER CROSS CSV CURRENT_P CURRENT_DATE CURRENT_ROLE
|
||||||
CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
|
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
|
||||||
|
|
||||||
DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
|
DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
|
||||||
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS
|
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS
|
||||||
@ -5715,7 +5715,7 @@ returning_clause:
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
DeleteStmt: DELETE_P FROM relation_expr_opt_alias
|
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);
|
DeleteStmt *n = makeNode(DeleteStmt);
|
||||||
n->relation = $3;
|
n->relation = $3;
|
||||||
@ -5771,7 +5771,7 @@ opt_nowait: NOWAIT { $$ = TRUE; }
|
|||||||
UpdateStmt: UPDATE relation_expr_opt_alias
|
UpdateStmt: UPDATE relation_expr_opt_alias
|
||||||
SET set_clause_list
|
SET set_clause_list
|
||||||
from_clause
|
from_clause
|
||||||
where_clause
|
where_or_current_clause
|
||||||
returning_clause
|
returning_clause
|
||||||
{
|
{
|
||||||
UpdateStmt *n = makeNode(UpdateStmt);
|
UpdateStmt *n = makeNode(UpdateStmt);
|
||||||
@ -6562,6 +6562,18 @@ where_clause:
|
|||||||
| /*EMPTY*/ { $$ = NULL; }
|
| /*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:
|
TableFuncElementList:
|
||||||
TableFuncElement
|
TableFuncElement
|
||||||
@ -8818,6 +8830,7 @@ unreserved_keyword:
|
|||||||
| CREATEROLE
|
| CREATEROLE
|
||||||
| CREATEUSER
|
| CREATEUSER
|
||||||
| CSV
|
| CSV
|
||||||
|
| CURRENT_P
|
||||||
| CURSOR
|
| CURSOR
|
||||||
| CYCLE
|
| CYCLE
|
||||||
| DATABASE
|
| DATABASE
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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},
|
{"createuser", CREATEUSER},
|
||||||
{"cross", CROSS},
|
{"cross", CROSS},
|
||||||
{"csv", CSV},
|
{"csv", CSV},
|
||||||
|
{"current", CURRENT_P},
|
||||||
{"current_date", CURRENT_DATE},
|
{"current_date", CURRENT_DATE},
|
||||||
{"current_role", CURRENT_ROLE},
|
{"current_role", CURRENT_ROLE},
|
||||||
{"current_time", CURRENT_TIME},
|
{"current_time", CURRENT_TIME},
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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);
|
result = transformBooleanTest(pstate, (BooleanTest *) expr);
|
||||||
break;
|
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
|
* Quietly accept node types that may be presented when we are
|
||||||
* called on an already-transformed tree.
|
* called on an already-transformed tree.
|
||||||
@ -1863,6 +1878,9 @@ exprType(Node *expr)
|
|||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
type = ((SetToDefault *) expr)->typeId;
|
type = ((SetToDefault *) expr)->typeId;
|
||||||
break;
|
break;
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
type = BOOLOID;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
|
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
|
||||||
type = InvalidOid; /* keep compiler quiet */
|
type = InvalidOid; /* keep compiler quiet */
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
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))
|
if (IsA(node, RangeTblRef))
|
||||||
{
|
{
|
||||||
RangeTblRef *rtr = (RangeTblRef *) node;
|
RangeTblRef *rtr = (RangeTblRef *) node;
|
||||||
@ -302,6 +310,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
|||||||
}
|
}
|
||||||
return false;
|
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))
|
if (IsA(node, RangeTblRef))
|
||||||
{
|
{
|
||||||
RangeTblRef *rtr = (RangeTblRef *) node;
|
RangeTblRef *rtr = (RangeTblRef *) node;
|
||||||
@ -466,6 +483,13 @@ IncrementVarSublevelsUp_walker(Node *node,
|
|||||||
var->varlevelsup += context->delta_sublevels_up;
|
var->varlevelsup += context->delta_sublevels_up;
|
||||||
return false; /* done here */
|
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))
|
if (IsA(node, Aggref))
|
||||||
{
|
{
|
||||||
Aggref *agg = (Aggref *) node;
|
Aggref *agg = (Aggref *) node;
|
||||||
@ -536,6 +560,15 @@ rangeTableEntry_used_walker(Node *node,
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
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))
|
if (IsA(node, RangeTblRef))
|
||||||
{
|
{
|
||||||
RangeTblRef *rtr = (RangeTblRef *) node;
|
RangeTblRef *rtr = (RangeTblRef *) node;
|
||||||
@ -932,8 +965,27 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
|
|||||||
}
|
}
|
||||||
/* otherwise fall through to copy the var normally */
|
/* 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 */
|
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
|
||||||
Query *newnode;
|
Query *newnode;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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_Param:
|
||||||
case T_CoerceToDomainValue:
|
case T_CoerceToDomainValue:
|
||||||
case T_SetToDefault:
|
case T_SetToDefault:
|
||||||
|
case T_CurrentOfExpr:
|
||||||
/* single words: always simple */
|
/* single words: always simple */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -4134,6 +4135,11 @@ get_rule_expr(Node *node, deparse_context *context,
|
|||||||
appendStringInfo(buf, "DEFAULT");
|
appendStringInfo(buf, "DEFAULT");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case T_CurrentOfExpr:
|
||||||
|
appendStringInfo(buf, "CURRENT OF %s",
|
||||||
|
quote_identifier(((CurrentOfExpr *) node)->cursor_name));
|
||||||
|
break;
|
||||||
|
|
||||||
case T_List:
|
case T_List:
|
||||||
{
|
{
|
||||||
char *sep;
|
char *sep;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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 ExecSupportsBackwardScan(Plan *node);
|
||||||
extern bool ExecMayReturnRawTuples(PlanState *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
|
* prototypes from functions in execGrouping.c
|
||||||
*/
|
*/
|
||||||
@ -135,6 +141,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
|
|||||||
TupleTableSlot *slot, EState *estate);
|
TupleTableSlot *slot, EState *estate);
|
||||||
extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
|
extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti,
|
||||||
ItemPointer tid, TransactionId priorXmax, CommandId curCid);
|
ItemPointer tid, TransactionId priorXmax, CommandId curCid);
|
||||||
|
extern PlanState *ExecGetActivePlanTree(QueryDesc *queryDesc);
|
||||||
extern DestReceiver *CreateIntoRelDestReceiver(void);
|
extern DestReceiver *CreateIntoRelDestReceiver(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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_CoerceToDomain,
|
||||||
T_CoerceToDomainValue,
|
T_CoerceToDomainValue,
|
||||||
T_SetToDefault,
|
T_SetToDefault,
|
||||||
|
T_CurrentOfExpr,
|
||||||
T_TargetEntry,
|
T_TargetEntry,
|
||||||
T_RangeTblRef,
|
T_RangeTblRef,
|
||||||
T_JoinExpr,
|
T_JoinExpr,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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 */
|
int32 typeMod; /* typemod for substituted value */
|
||||||
} SetToDefault;
|
} 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 -
|
* TargetEntry -
|
||||||
* a target entry (used in query target lists)
|
* a target entry (used in query target lists)
|
||||||
|
@ -899,3 +899,176 @@ SELECT name FROM pg_cursors ORDER BY 1;
|
|||||||
(0 rows)
|
(0 rows)
|
||||||
|
|
||||||
COMMIT;
|
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;
|
||||||
|
@ -316,5 +316,85 @@ CLOSE ALL;
|
|||||||
SELECT name FROM pg_cursors ORDER BY 1;
|
SELECT name FROM pg_cursors ORDER BY 1;
|
||||||
COMMIT;
|
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user