From: Dean Rasheed Date: Wed, 22 Apr 2026 10:50:17 +0000 (+0100) Subject: Fix UPDATE/DELETE ... WHERE CURRENT OF on a table with virtual columns. X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=5548a969b65d4e0d5045d9db0bcac062a9a3e3c7;p=thirdparty%2Fpostgresql.git Fix UPDATE/DELETE ... WHERE CURRENT OF on a table with virtual columns. Formerly, attempting to use WHERE CURRENT OF to update or delete from a table with virtual generated columns would fail with the error "WHERE CURRENT OF on a view is not implemented". The reason was that the check preventing WHERE CURRENT OF from being used on a view was in replace_rte_variables_mutator(), which presumed that the only way it could get there was as part of rewriting a query on a view. That is no longer the case, since replace_rte_variables() is now also used to expand the virtual generated columns of a table. Fix by doing the check for WHERE CURRENT OF on a view at parse time. This is safe, since it is no longer possible for the relkind to change after the query is parsed (as of b23cd185f). Reported-by: Satyanarayana Narlapuram Author: Satyanarayana Narlapuram Author: Dean Rasheed Discussion: https://postgr.es/m/CAHg+QDc_TwzSgb=B_QgNLt3mvZdmRK23rLb+RkanSQkDF40GjA@mail.gmail.com Backpatch-through: 18 --- diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index cb4e5019c2f..ffcf25a6be7 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -595,6 +595,14 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) ACL_DELETE); nsitem = pstate->p_target_nsitem; + /* disallow DELETE ... WHERE CURRENT OF on a view */ + if (stmt->whereClause && + IsA(stmt->whereClause, CurrentOfExpr) && + pstate->p_target_relation->rd_rel->relkind == RELKIND_VIEW) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WHERE CURRENT OF on a view is not implemented")); + /* there's no DISTINCT in DELETE */ qry->distinctClause = NIL; @@ -2868,6 +2876,14 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) true, ACL_UPDATE); + /* disallow UPDATE ... WHERE CURRENT OF on a view */ + if (stmt->whereClause && + IsA(stmt->whereClause, CurrentOfExpr) && + pstate->p_target_relation->rd_rel->relkind == RELKIND_VIEW) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WHERE CURRENT OF on a view is not implemented")); + if (stmt->forPortionOf) qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 4bf4aa0d6d1..9aa7ef60475 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -1514,25 +1514,6 @@ replace_rte_variables_mutator(Node *node, } /* otherwise fall through to copy the var normally */ } - else if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) node; - - if (cexpr->cvarno == 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 */ diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 262c062417d..24d5dbf46ca 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -1805,3 +1805,33 @@ insert into gtest34p values (1, 2) (1 row) drop table gtest34p; +-- Ensure that virtual generated columns work with WHERE CURRENT OF +create table gtest_cursor (id int primary key, a int, b int generated always as (a * 2) virtual); +insert into gtest_cursor values (1, 10), (2, 20), (3, 30); +begin; +declare curs cursor for select * from gtest_cursor order by id for update; +fetch 1 from curs; + id | a | b +----+----+---- + 1 | 10 | 20 +(1 row) + +update gtest_cursor set a = 99 where current of curs; +select * from gtest_cursor order by id; + id | a | b +----+----+----- + 1 | 99 | 198 + 2 | 20 | 40 + 3 | 30 | 60 +(3 rows) + +delete from gtest_cursor where current of curs; +select * from gtest_cursor order by id; + id | a | b +----+----+---- + 2 | 20 | 40 + 3 | 30 | 60 +(2 rows) + +commit; +drop table gtest_cursor; diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out index 31f77abc446..a66f27e3694 100644 --- a/src/test/regress/expected/portals.out +++ b/src/test/regress/expected/portals.out @@ -1336,6 +1336,17 @@ FETCH FROM c1; DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported ERROR: WHERE CURRENT OF on a view is not implemented ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM ucview; +FETCH FROM c1; + f1 | f2 +----+------- + 13 | three +(1 row) + +UPDATE ucview SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail, views not supported +ERROR: WHERE CURRENT OF on a view is not implemented +ROLLBACK; -- Check WHERE CURRENT OF with an index-only scan BEGIN; EXPLAIN (costs off) diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index 1746807bd3d..9c2bb6590b3 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -954,3 +954,18 @@ insert into gtest34p values (1, 7) insert into gtest34p values (1, 2) on conflict (id) do update set a = gtest34p.c + excluded.c returning *; drop table gtest34p; + +-- Ensure that virtual generated columns work with WHERE CURRENT OF +create table gtest_cursor (id int primary key, a int, b int generated always as (a * 2) virtual); +insert into gtest_cursor values (1, 10), (2, 20), (3, 30); + +begin; +declare curs cursor for select * from gtest_cursor order by id for update; +fetch 1 from curs; +update gtest_cursor set a = 99 where current of curs; +select * from gtest_cursor order by id; +delete from gtest_cursor where current of curs; +select * from gtest_cursor order by id; +commit; + +drop table gtest_cursor; diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql index fc4cccb96c0..196b862c756 100644 --- a/src/test/regress/sql/portals.sql +++ b/src/test/regress/sql/portals.sql @@ -508,6 +508,11 @@ DECLARE c1 CURSOR FOR SELECT * FROM ucview; FETCH FROM c1; DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM ucview; +FETCH FROM c1; +UPDATE ucview SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail, views not supported +ROLLBACK; -- Check WHERE CURRENT OF with an index-only scan BEGIN;