Forbid FOR PORTION OF with WHERE CURRENT OF

It is not clear how the implicit condition of FOR PORTION OF should
interact with the use of a cursor.  Normally, we forbid combining
WHERE CURRENT OF with other WHERE conditions.  The SQL standard only
includes FOR PORTION OF with <update statement: searched> and <delete
statement: searched>, not <update statement: positioned> or <delete
statement: positioned>, so it is easy for us to exclude the
functionality, at least for now.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Discussion: https://www.postgresql.org/message-id/flat/CA%2BrenyUEKPexUYsH4qeU8_o1jqKsUkEWca1keS6n21shgG1g%2BA%40mail.gmail.com
This commit is contained in:
Peter Eisentraut 2026-06-29 15:13:45 +02:00
parent 994f770a0f
commit bc3ae886a7
5 changed files with 86 additions and 8 deletions

View file

@ -231,10 +231,9 @@ DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ *
from this cursor. The cursor must be a non-grouping
query on the <command>DELETE</command>'s target table.
Note that <literal>WHERE CURRENT OF</literal> cannot be
specified together with a Boolean condition. See
<xref linkend="sql-declare"/>
for more information about using cursors with
<literal>WHERE CURRENT OF</literal>.
specified together with a Boolean condition or <literal>FOR PORTION
OF</literal>. See <xref linkend="sql-declare"/> for more information
about using cursors with <literal>WHERE CURRENT OF</literal>.
</para>
</listitem>
</varlistentry>

View file

@ -287,10 +287,9 @@ UPDATE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
from this cursor. The cursor must be a non-grouping
query on the <command>UPDATE</command>'s target table.
Note that <literal>WHERE CURRENT OF</literal> cannot be
specified together with a Boolean condition. See
<xref linkend="sql-declare"/>
for more information about using cursors with
<literal>WHERE CURRENT OF</literal>.
specified together with a Boolean condition or <literal>FOR PORTION
OF</literal>. See <xref linkend="sql-declare"/> for more information
about using cursors with <literal>WHERE CURRENT OF</literal>.
</para>
</listitem>
</varlistentry>

View file

@ -81,6 +81,7 @@ static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
static ForPortionOfExpr *transformForPortionOfClause(ParseState *pstate,
int rtindex,
const ForPortionOfClause *forPortionOf,
const Node *whereClause,
bool isUpdate);
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt,
@ -626,6 +627,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
qry->forPortionOf = transformForPortionOfClause(pstate,
qry->resultRelation,
stmt->forPortionOf,
stmt->whereClause,
false);
qual = transformWhereClause(pstate, stmt->whereClause,
@ -1319,6 +1321,7 @@ static ForPortionOfExpr *
transformForPortionOfClause(ParseState *pstate,
int rtindex,
const ForPortionOfClause *forPortionOf,
const Node *whereClause,
bool isUpdate)
{
Relation targetrel = pstate->p_target_relation;
@ -1335,6 +1338,12 @@ transformForPortionOfClause(ParseState *pstate,
ForPortionOfExpr *result;
Var *rangeVar;
/* disallow FOR PORTION OF ... WHERE CURRENT OF */
if (whereClause && IsA(whereClause, CurrentOfExpr))
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("WHERE CURRENT OF with FOR PORTION OF is not implemented"));
result = makeNode(ForPortionOfExpr);
/* Look up the FOR PORTION OF name requested. */
@ -2875,6 +2884,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
qry->forPortionOf = transformForPortionOfClause(pstate,
qry->resultRelation,
stmt->forPortionOf,
stmt->whereClause,
true);
nsitem = pstate->p_target_nsitem;

View file

@ -2446,4 +2446,46 @@ NOTICE: fpo_before_row1: BEFORE UPDATE ROW:
NOTICE: old: [10,100)
NOTICE: new: [30,70)
DROP TABLE fpo_update_of_trigger;
-- CURSORs
CREATE TABLE fpo_cursed (
id int,
valid_at int4range
);
INSERT INTO fpo_cursed (id, valid_at) VALUES (1, '[10,100)');
-- UPDATE FOR PORTION OF is not permitted with a CURSOR:
BEGIN;
DECLARE fpo_cur CURSOR FOR SELECT * FROM fpo_cursed;
FETCH NEXT FROM fpo_cur;
id | valid_at
----+----------
1 | [10,100)
(1 row)
UPDATE fpo_cursed
FOR PORTION OF valid_at FROM 5 TO 6
SET id = 2
WHERE CURRENT OF fpo_cur;
ERROR: WHERE CURRENT OF with FOR PORTION OF is not implemented
ROLLBACK;
-- DELETE FOR PORTION OF is not permitted with a CURSOR:
BEGIN;
DECLARE fpo_cur CURSOR FOR SELECT * FROM fpo_cursed;
FETCH NEXT FROM fpo_cur;
id | valid_at
----+----------
1 | [10,100)
(1 row)
DELETE FROM fpo_cursed
FOR PORTION OF valid_at FROM 8 TO 9
WHERE CURRENT OF fpo_cur;
ERROR: WHERE CURRENT OF with FOR PORTION OF is not implemented
ROLLBACK;
SELECT * FROM fpo_cursed;
id | valid_at
----+----------
1 | [10,100)
(1 row)
DROP TABLE fpo_cursed;
RESET datestyle;

View file

@ -1591,4 +1591,32 @@ UPDATE fpo_update_of_trigger
SET id = 2;
DROP TABLE fpo_update_of_trigger;
-- CURSORs
CREATE TABLE fpo_cursed (
id int,
valid_at int4range
);
INSERT INTO fpo_cursed (id, valid_at) VALUES (1, '[10,100)');
-- UPDATE FOR PORTION OF is not permitted with a CURSOR:
BEGIN;
DECLARE fpo_cur CURSOR FOR SELECT * FROM fpo_cursed;
FETCH NEXT FROM fpo_cur;
UPDATE fpo_cursed
FOR PORTION OF valid_at FROM 5 TO 6
SET id = 2
WHERE CURRENT OF fpo_cur;
ROLLBACK;
-- DELETE FOR PORTION OF is not permitted with a CURSOR:
BEGIN;
DECLARE fpo_cur CURSOR FOR SELECT * FROM fpo_cursed;
FETCH NEXT FROM fpo_cur;
DELETE FROM fpo_cursed
FOR PORTION OF valid_at FROM 8 TO 9
WHERE CURRENT OF fpo_cur;
ROLLBACK;
SELECT * FROM fpo_cursed;
DROP TABLE fpo_cursed;
RESET datestyle;