diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index 60215eba3b8..2c08f7e3cff 100644
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -197,11 +197,12 @@ CREATE VIEW [ schema . ] view_nameCHECK OPTION is not specified,
- INSERT, UPDATE, and
- MERGE commands on the view are
- allowed to create rows that are not visible through the view. The
- following check options are supported:
+ rejected. The temporal
+ leftovers inserted by an UPDATE or
+ DELETE with a FOR PORTION OF clause
+ are checked in the same way. If the CHECK OPTION is
+ not specified, these commands are allowed to create rows that are not
+ visible through the view. The following check options are supported:
@@ -431,10 +432,13 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
potentially insert base-relation rows
that do not satisfy the WHERE condition and thus are not
visible through the view (ON CONFLICT DO SELECT/UPDATE may
- similarly affect an existing row not visible through the view).
+ similarly affect an existing row not visible through the view). An
+ UPDATE or DELETE with a
+ FOR PORTION OF clause can do so as well, since the
+ temporal leftovers it inserts may fall outside the
+ WHERE condition.
The CHECK OPTION may be used to prevent
- INSERT, UPDATE, and
- MERGE commands from creating such rows that are not
+ these commands from creating such rows that are not
visible through the view.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 38f54b57eec..3e43418e996 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3953,8 +3953,14 @@ rewriteTargetView(Query *parsetree, Relation view)
* the WITH CHECK OPTION, or any parent view specified WITH CASCADED CHECK
* OPTION, add the quals from the view to the query's withCheckOptions
* list.
+ *
+ * DELETE FOR PORTION OF needs this too: it inserts temporal leftovers to
+ * preserve the untouched parts of the deleted row, and those must not
+ * escape the view either. For UPDATE, any WCO we add below will apply to
+ * inserted leftovers as well.
*/
- if (insert_or_update)
+ if (insert_or_update ||
+ (parsetree->commandType == CMD_DELETE && parsetree->forPortionOf != NULL))
{
bool has_wco = RelationHasCheckOption(view);
bool cascaded = RelationHasCascadedCheckOption(view);
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 9c6bb2219f9..b4b4e93a7dd 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -3768,6 +3768,62 @@ delete from uv_fpo_view_nonupd for portion of valid_at from 1 to 10;
ERROR: cannot delete from view "uv_fpo_view_nonupd" using FOR PORTION OF "valid_at"
DETAIL: View columns that are not columns of their base relation are not updatable.
drop view uv_fpo_view_nonupd;
+-- WITH CHECK OPTION must be enforced on temporal leftovers, i.e. the rows
+-- FOR PORTION OF inserts to preserve the untouched parts of the target row.
+-- This applies to DELETE as well as UPDATE.
+create table uv_fpo_wco_tab (id int4range, valid_at daterange, b int);
+insert into uv_fpo_wco_tab values ('[1,1]', '[2020-01-01,2030-01-01)', 0);
+create view uv_fpo_wco_view as
+ select * from uv_fpo_wco_tab
+ where valid_at && daterange('2024-01-01', '2025-01-01')
+ with check option;
+-- The leftovers fall outside the view, so both commands fail:
+update uv_fpo_wco_view for portion of valid_at from '2024-01-01' to '2025-01-01' set b = 1;
+ERROR: new row violates check option for view "uv_fpo_wco_view"
+DETAIL: Failing row contains ([1,2), [01-01-2020,01-01-2024), 0).
+delete from uv_fpo_wco_view for portion of valid_at from '2024-01-01' to '2025-01-01';
+ERROR: new row violates check option for view "uv_fpo_wco_view"
+DETAIL: Failing row contains ([1,2), [01-01-2020,01-01-2024), 0).
+-- The base table is unchanged:
+select * from uv_fpo_wco_tab order by valid_at;
+ id | valid_at | b
+-------+-------------------------+---
+ [1,2) | [01-01-2020,01-01-2030) | 0
+(1 row)
+
+-- Leftovers that still satisfy the view are allowed:
+delete from uv_fpo_wco_view for portion of valid_at from '2024-03-01' to '2024-06-01';
+update uv_fpo_wco_view for portion of valid_at from '2024-07-01' to '2024-08-01' set b = 1;
+select * from uv_fpo_wco_tab order by valid_at;
+ id | valid_at | b
+-------+-------------------------+---
+ [1,2) | [01-01-2020,03-01-2024) | 0
+ [1,2) | [06-01-2024,07-01-2024) | 0
+ [1,2) | [07-01-2024,08-01-2024) | 1
+ [1,2) | [08-01-2024,01-01-2030) | 0
+(4 rows)
+
+-- Without WITH CHECK OPTION the leftovers may leave the view:
+create view uv_fpo_nowco_view as
+ select * from uv_fpo_wco_tab
+ where valid_at && daterange('2024-01-01', '2025-01-01');
+delete from uv_fpo_nowco_view for portion of valid_at from '2024-01-01' to '2024-02-01';
+update uv_fpo_nowco_view for portion of valid_at from '2024-09-01' to '2026-01-01' set b = 2;
+select * from uv_fpo_wco_tab order by valid_at;
+ id | valid_at | b
+-------+-------------------------+---
+ [1,2) | [01-01-2020,01-01-2024) | 0
+ [1,2) | [02-01-2024,03-01-2024) | 0
+ [1,2) | [06-01-2024,07-01-2024) | 0
+ [1,2) | [07-01-2024,08-01-2024) | 1
+ [1,2) | [08-01-2024,09-01-2024) | 0
+ [1,2) | [09-01-2024,01-01-2026) | 2
+ [1,2) | [01-01-2026,01-01-2030) | 0
+(7 rows)
+
+drop view uv_fpo_wco_view;
+drop view uv_fpo_nowco_view;
+drop table uv_fpo_wco_tab;
-- Test whole-row references to the view
create table uv_iocu_tab (a int unique, b text);
create view uv_iocu_view as
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index 2ef9aa32f36..3ddb7b43cc8 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1914,6 +1914,35 @@ update uv_fpo_view_nonupd for portion of valid_at from 1 to 10 set b = 2;
delete from uv_fpo_view_nonupd for portion of valid_at from 1 to 10;
drop view uv_fpo_view_nonupd;
+-- WITH CHECK OPTION must be enforced on temporal leftovers, i.e. the rows
+-- FOR PORTION OF inserts to preserve the untouched parts of the target row.
+-- This applies to DELETE as well as UPDATE.
+create table uv_fpo_wco_tab (id int4range, valid_at daterange, b int);
+insert into uv_fpo_wco_tab values ('[1,1]', '[2020-01-01,2030-01-01)', 0);
+create view uv_fpo_wco_view as
+ select * from uv_fpo_wco_tab
+ where valid_at && daterange('2024-01-01', '2025-01-01')
+ with check option;
+-- The leftovers fall outside the view, so both commands fail:
+update uv_fpo_wco_view for portion of valid_at from '2024-01-01' to '2025-01-01' set b = 1;
+delete from uv_fpo_wco_view for portion of valid_at from '2024-01-01' to '2025-01-01';
+-- The base table is unchanged:
+select * from uv_fpo_wco_tab order by valid_at;
+-- Leftovers that still satisfy the view are allowed:
+delete from uv_fpo_wco_view for portion of valid_at from '2024-03-01' to '2024-06-01';
+update uv_fpo_wco_view for portion of valid_at from '2024-07-01' to '2024-08-01' set b = 1;
+select * from uv_fpo_wco_tab order by valid_at;
+-- Without WITH CHECK OPTION the leftovers may leave the view:
+create view uv_fpo_nowco_view as
+ select * from uv_fpo_wco_tab
+ where valid_at && daterange('2024-01-01', '2025-01-01');
+delete from uv_fpo_nowco_view for portion of valid_at from '2024-01-01' to '2024-02-01';
+update uv_fpo_nowco_view for portion of valid_at from '2024-09-01' to '2026-01-01' set b = 2;
+select * from uv_fpo_wco_tab order by valid_at;
+drop view uv_fpo_wco_view;
+drop view uv_fpo_nowco_view;
+drop table uv_fpo_wco_tab;
+
-- Test whole-row references to the view
create table uv_iocu_tab (a int unique, b text);
create view uv_iocu_view as