From 44134aa2b0c8d026b3ed2eafd37fae462d434794 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Tue, 27 Oct 2026 21:42:21 -0700
Subject: [PATCH v1 5/6] Allow FOR PORTION OF on views with DO ALSO rules

After the previous commit, DO ALSO rule actions on a view see the FOR
PORTION OF restriction too, so removed the error. The view is still
auto-updated and inserts leftovers as usual.

Reported-by: Andres Freund <andres@anarazel.de>
Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Discussion: https://postgr.es/m/yfers7d2kizq6ockz4nu7e7avq3gj5anpd7ilxkazyvicv4qgy%403cesow2bbhnx
---
 doc/src/sgml/dml.sgml                         | 18 +++--
 src/backend/rewrite/rewriteHandler.c          |  6 --
 src/test/regress/expected/updatable_views.out | 80 ++++++++++++-------
 src/test/regress/sql/updatable_views.sql      | 56 ++++++++-----
 4 files changed, 97 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/dml.sgml b/doc/src/sgml/dml.sgml
index 18d819121d8..d7c1fc08374 100644
--- a/doc/src/sgml/dml.sgml
+++ b/doc/src/sgml/dml.sgml
@@ -397,14 +397,16 @@ DELETE FROM products
   </para>
 
   <para>
-   If a temporal update or delete is replaced by an unconditional <literal>DO
-   INSTEAD</literal> rule (see <xref linkend="rules-update"/>), the rule's
-   actions run only for rows that overlap the targeted portion.  In those
-   actions, the application-time value in <literal>NEW</literal> is narrowed
-   based on the <literal>FOR PORTION OF</literal> target, while
-   <literal>OLD</literal> has the original value.  The original command does
-   not run, so no temporal leftovers are inserted.  This is the same whether
-   the rule is on a table or a view.
+   The actions of <literal>ON UPDATE</literal> and <literal>ON
+   DELETE</literal> rules (see <xref linkend="rules-update"/>) run only for
+   rows that overlap the targeted portion.  In those actions, the
+   application-time value in <literal>NEW</literal> is narrowed based on the
+   <literal>FOR PORTION OF</literal> target, while <literal>OLD</literal> has
+   the original value.  With a <literal>DO ALSO</literal> rule the original
+   command runs as well and inserts temporal leftovers as usual, but if the
+   command is replaced by an unconditional <literal>DO INSTEAD</literal> rule,
+   it does not run, so no temporal leftovers are inserted.  This is the same
+   whether the rule is on a table or a view.
   </para>
 
   <para>
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 1db1e2188fd..5bf696960f0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -4493,12 +4493,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length,
 										 parsetree->mergeActionList,
 										 gettext_noop("Views with conditional DO INSTEAD rules are not automatically updatable."));
 
-			/* We don't support FOR PORTION OF on views with DO ALSO rules. */
-			if (parsetree->forPortionOf && product_queries != NIL)
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("views with DO ALSO rules do not support FOR PORTION OF")));
-
 			/*
 			 * Attempt to rewrite the query to automatically update the view.
 			 * This throws an error if the view can't be automatically
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 77c68640e92..80531078fd1 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -4414,39 +4414,63 @@ delete from uv_fpo_instead_view
 ERROR:  views with INSTEAD OF triggers do not support FOR PORTION OF
 drop view uv_fpo_instead_view;
 drop function uv_fpo_instead_trig();
--- FOR PORTION OF is not supported on views with DO ALSO rules either.  The
--- rule action is built before we narrow the view's query, so it would see an
--- unnarrowed NEW range and would not be limited to the targeted rows.
-create table uv_fpo_also_log (tag text, valid_at tsrange, b float);
-create view uv_fpo_also_view as select id, valid_at, b from uv_fpo_tab;
+-- FOR PORTION OF on a view with DO ALSO rules.  As on a table, the rule
+-- actions fire only for rows that overlap the target range, NEW holds the
+-- targeted portion of the range, and OLD holds the whole row.  The view is
+-- still auto-updated, inserting temporal leftovers.  Here it's a
+-- security-barrier view on a partitioned table, and one leftover moves to
+-- the other partition.
+create table uv_fpo_also_vtab (id int4range, valid_at daterange, b int)
+  partition by range (lower(valid_at));
+create table uv_fpo_also_vtab1 partition of uv_fpo_also_vtab
+  for values from ('2000-01-01') to ('2005-01-01');
+create table uv_fpo_also_vtab2 partition of uv_fpo_also_vtab
+  for values from ('2005-01-01') to (maxvalue);
+insert into uv_fpo_also_vtab values
+  ('[1,1]', '[2000-01-01,2010-01-01)', 0),
+  ('[2,2]', '[2020-01-01,2030-01-01)', 0);
+create table uv_fpo_also_log (tag text, old_valid_at daterange,
+  new_valid_at daterange, b int);
+create view uv_fpo_also_view with (security_barrier) as
+  select id, valid_at, b from uv_fpo_also_vtab;
 create rule uv_fpo_also_upd as on update to uv_fpo_also_view
-  do also insert into uv_fpo_also_log values ('upd', new.valid_at, new.b);
+  do also insert into uv_fpo_also_log
+    values ('upd', old.valid_at, new.valid_at, new.b);
 create rule uv_fpo_also_del as on delete to uv_fpo_also_view
-  do also insert into uv_fpo_also_log values ('del', old.valid_at, old.b);
+  do also insert into uv_fpo_also_log
+    values ('del', old.valid_at, null, old.b);
 update uv_fpo_also_view
-  for portion of valid_at from '2015-01-01' to '2020-01-01'
-  set b = 99 where id = '[1,1]'; -- error
-ERROR:  views with DO ALSO rules do not support FOR PORTION OF
+  for portion of valid_at from '2002-01-01' to '2006-01-01' set b = 1;
 delete from uv_fpo_also_view
-  for portion of valid_at from '2017-01-01' to '2022-01-01'
-  where id = '[1,1]'; -- error
-ERROR:  views with DO ALSO rules do not support FOR PORTION OF
--- Without FOR PORTION OF the rules work as always.
-update uv_fpo_also_view set b = 98 where id = '[1,1]';
-delete from uv_fpo_also_view where id = '[1,1]';
-select * from uv_fpo_also_log order by tag, valid_at;
- tag |                        valid_at                         | b  
------+---------------------------------------------------------+----
- del | ["Fri Jan 01 00:00:00 2010","Thu Jan 01 00:00:00 2015") | 98
- del | ["Thu Jan 01 00:00:00 2015","Sun Jan 01 00:00:00 2017") | 98
- del | ["Sat Jan 01 00:00:00 2022","Tue Jan 01 00:00:00 2030") | 98
- upd | ["Fri Jan 01 00:00:00 2010","Thu Jan 01 00:00:00 2015") | 98
- upd | ["Thu Jan 01 00:00:00 2015","Sun Jan 01 00:00:00 2017") | 98
- upd | ["Sat Jan 01 00:00:00 2022","Tue Jan 01 00:00:00 2030") | 98
-(6 rows)
+  for portion of valid_at from '2007-01-01' to '2008-01-01';
+update uv_fpo_also_view
+  for portion of valid_at from '2012-01-01' to '2014-01-01' set b = 2;
+-- through an auto-updatable view with the columns in a different order
+create view uv_fpo_also_outer as select b, valid_at, id from uv_fpo_also_view;
+update uv_fpo_also_outer
+  for portion of valid_at from '2024-01-01' to '2026-01-01' set b = 3;
+select * from uv_fpo_also_log order by tag, old_valid_at;
+ tag |      old_valid_at       |      new_valid_at       | b 
+-----+-------------------------+-------------------------+---
+ del | [01-01-2006,01-01-2010) |                         | 0
+ upd | [01-01-2000,01-01-2010) | [01-01-2002,01-01-2006) | 1
+ upd | [01-01-2020,01-01-2030) | [01-01-2024,01-01-2026) | 3
+(3 rows)
+
+select tableoid::regclass, * from uv_fpo_also_vtab order by id, valid_at;
+     tableoid      |  id   |        valid_at         | b 
+-------------------+-------+-------------------------+---
+ uv_fpo_also_vtab1 | [1,2) | [01-01-2000,01-01-2002) | 0
+ uv_fpo_also_vtab1 | [1,2) | [01-01-2002,01-01-2006) | 1
+ uv_fpo_also_vtab2 | [1,2) | [01-01-2006,01-01-2007) | 0
+ uv_fpo_also_vtab2 | [1,2) | [01-01-2008,01-01-2010) | 0
+ uv_fpo_also_vtab2 | [2,3) | [01-01-2020,01-01-2024) | 0
+ uv_fpo_also_vtab2 | [2,3) | [01-01-2024,01-01-2026) | 3
+ uv_fpo_also_vtab2 | [2,3) | [01-01-2026,01-01-2030) | 0
+(7 rows)
 
-drop view uv_fpo_also_view;
-drop table uv_fpo_also_log;
+drop view uv_fpo_also_outer, uv_fpo_also_view;
+drop table uv_fpo_also_vtab, uv_fpo_also_log;
 -- A DO ALSO rule on the base table is fine: the rule action is built from a
 -- query that already carries the FOR PORTION OF restriction.
 create table uv_fpo_also_tab (id int4range, valid_at tsrange, b float);
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index afccdcd8143..3a60d47994b 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -2323,31 +2323,45 @@ delete from uv_fpo_instead_view
 drop view uv_fpo_instead_view;
 drop function uv_fpo_instead_trig();
 
--- FOR PORTION OF is not supported on views with DO ALSO rules either.  The
--- rule action is built before we narrow the view's query, so it would see an
--- unnarrowed NEW range and would not be limited to the targeted rows.
-create table uv_fpo_also_log (tag text, valid_at tsrange, b float);
-create view uv_fpo_also_view as select id, valid_at, b from uv_fpo_tab;
+-- FOR PORTION OF on a view with DO ALSO rules.  As on a table, the rule
+-- actions fire only for rows that overlap the target range, NEW holds the
+-- targeted portion of the range, and OLD holds the whole row.  The view is
+-- still auto-updated, inserting temporal leftovers.  Here it's a
+-- security-barrier view on a partitioned table, and one leftover moves to
+-- the other partition.
+create table uv_fpo_also_vtab (id int4range, valid_at daterange, b int)
+  partition by range (lower(valid_at));
+create table uv_fpo_also_vtab1 partition of uv_fpo_also_vtab
+  for values from ('2000-01-01') to ('2005-01-01');
+create table uv_fpo_also_vtab2 partition of uv_fpo_also_vtab
+  for values from ('2005-01-01') to (maxvalue);
+insert into uv_fpo_also_vtab values
+  ('[1,1]', '[2000-01-01,2010-01-01)', 0),
+  ('[2,2]', '[2020-01-01,2030-01-01)', 0);
+create table uv_fpo_also_log (tag text, old_valid_at daterange,
+  new_valid_at daterange, b int);
+create view uv_fpo_also_view with (security_barrier) as
+  select id, valid_at, b from uv_fpo_also_vtab;
 create rule uv_fpo_also_upd as on update to uv_fpo_also_view
-  do also insert into uv_fpo_also_log values ('upd', new.valid_at, new.b);
+  do also insert into uv_fpo_also_log
+    values ('upd', old.valid_at, new.valid_at, new.b);
 create rule uv_fpo_also_del as on delete to uv_fpo_also_view
-  do also insert into uv_fpo_also_log values ('del', old.valid_at, old.b);
-
+  do also insert into uv_fpo_also_log
+    values ('del', old.valid_at, null, old.b);
 update uv_fpo_also_view
-  for portion of valid_at from '2015-01-01' to '2020-01-01'
-  set b = 99 where id = '[1,1]'; -- error
-
+  for portion of valid_at from '2002-01-01' to '2006-01-01' set b = 1;
 delete from uv_fpo_also_view
-  for portion of valid_at from '2017-01-01' to '2022-01-01'
-  where id = '[1,1]'; -- error
-
--- Without FOR PORTION OF the rules work as always.
-update uv_fpo_also_view set b = 98 where id = '[1,1]';
-delete from uv_fpo_also_view where id = '[1,1]';
-select * from uv_fpo_also_log order by tag, valid_at;
-
-drop view uv_fpo_also_view;
-drop table uv_fpo_also_log;
+  for portion of valid_at from '2007-01-01' to '2008-01-01';
+update uv_fpo_also_view
+  for portion of valid_at from '2012-01-01' to '2014-01-01' set b = 2;
+-- through an auto-updatable view with the columns in a different order
+create view uv_fpo_also_outer as select b, valid_at, id from uv_fpo_also_view;
+update uv_fpo_also_outer
+  for portion of valid_at from '2024-01-01' to '2026-01-01' set b = 3;
+select * from uv_fpo_also_log order by tag, old_valid_at;
+select tableoid::regclass, * from uv_fpo_also_vtab order by id, valid_at;
+drop view uv_fpo_also_outer, uv_fpo_also_view;
+drop table uv_fpo_also_vtab, uv_fpo_also_log;
 
 -- A DO ALSO rule on the base table is fine: the rule action is built from a
 -- query that already carries the FOR PORTION OF restriction.
-- 
2.47.3

