From b0f83ff70a1e0ee2c544c3cd31a2c40adbc9e058 Mon Sep 17 00:00:00 2001 From: Aleksander Alekseev Date: Tue, 7 Jul 2026 17:20:05 -0700 Subject: [PATCH v8 1/4] Forbid FOR PORTION OF on views with INSTEAD OF triggers Previously an attempt to use these features together caused a crash. Oversight of commit 8e72d914c528. Author: Aleksander Alekseev Discussion: https://www.postgresql.org/message-id/CAJ7c6TME%2Bix6VRf-2TPnVTsj8qn_hy6sYAOmMhZEivwsu2wS6g%40mail.gmail.com --- src/backend/parser/analyze.c | 11 ++++++ src/test/regress/expected/updatable_views.out | 38 +++++++++++++++++++ src/test/regress/sql/updatable_views.sql | 34 +++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 2932d17a107..1e6fbc95964 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1344,6 +1344,17 @@ transformForPortionOfClause(ParseState *pstate, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("WHERE CURRENT OF with FOR PORTION OF is not implemented")); + /* We don't support FOR PORTION OF on views with INSTEAD OF triggers. */ + if (targetrel->rd_rel->relkind == RELKIND_VIEW && + targetrel->rd_rel->relhastriggers && + targetrel->trigdesc != NULL && + (isUpdate ? targetrel->trigdesc->trig_update_instead_row + : targetrel->trigdesc->trig_delete_instead_row)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("views with INSTEAD OF triggers do not support FOR PORTION OF"), + parser_errposition(pstate, forPortionOf->location))); + result = makeNode(ForPortionOfExpr); /* Look up the FOR PORTION OF name requested. */ diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 7b00c742776..acab165ae4c 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -4165,3 +4165,41 @@ select * from base_tab order by a; drop view base_tab_view; drop table base_tab; +-- FOR PORTION OF is not supported on views with INSTEAD OF triggers +create view uv_fpo_instead_view as select id, valid_at, b from uv_fpo_tab; +create function uv_fpo_instead_trig() returns trigger language plpgsql as +$$ begin return null; end $$; +create trigger uv_fpo_instead_upd_trig + instead of update on uv_fpo_instead_view + for each row execute function uv_fpo_instead_trig(); +create trigger uv_fpo_instead_del_trig + instead of delete on uv_fpo_instead_view + for each row execute function uv_fpo_instead_trig(); +update uv_fpo_instead_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 INSTEAD OF triggers do not support FOR PORTION OF +LINE 2: for portion of valid_at from '2015-01-01' to '2020-01-01' + ^ +delete from uv_fpo_instead_view + for portion of valid_at from '2017-01-01' to '2022-01-01' + where id = '[1,1]'; -- error +ERROR: views with INSTEAD OF triggers do not support FOR PORTION OF +LINE 2: for portion of valid_at from '2017-01-01' to '2022-01-01' + ^ +-- The check does not depend on which rows match, so it errors even when +-- no rows do. +update uv_fpo_instead_view + for portion of valid_at from '2015-01-01' to '2020-01-01' + set b = 99 where id = '[9,9]'; -- error, even with no matching rows +ERROR: views with INSTEAD OF triggers do not support FOR PORTION OF +LINE 2: for portion of valid_at from '2015-01-01' to '2020-01-01' + ^ +delete from uv_fpo_instead_view + for portion of valid_at from '2017-01-01' to '2022-01-01' + where id = '[9,9]'; -- error, even with no matching rows +ERROR: views with INSTEAD OF triggers do not support FOR PORTION OF +LINE 2: for portion of valid_at from '2017-01-01' to '2022-01-01' + ^ +drop view uv_fpo_instead_view; +drop function uv_fpo_instead_trig(); diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index 4a60126ec90..f4a7e0327b0 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -2148,3 +2148,37 @@ values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12); select * from base_tab order by a; drop view base_tab_view; drop table base_tab; + +-- FOR PORTION OF is not supported on views with INSTEAD OF triggers +create view uv_fpo_instead_view as select id, valid_at, b from uv_fpo_tab; + +create function uv_fpo_instead_trig() returns trigger language plpgsql as +$$ begin return null; end $$; + +create trigger uv_fpo_instead_upd_trig + instead of update on uv_fpo_instead_view + for each row execute function uv_fpo_instead_trig(); +create trigger uv_fpo_instead_del_trig + instead of delete on uv_fpo_instead_view + for each row execute function uv_fpo_instead_trig(); + +update uv_fpo_instead_view + for portion of valid_at from '2015-01-01' to '2020-01-01' + set b = 99 where id = '[1,1]'; -- error + +delete from uv_fpo_instead_view + for portion of valid_at from '2017-01-01' to '2022-01-01' + where id = '[1,1]'; -- error + +-- The check does not depend on which rows match, so it errors even when +-- no rows do. +update uv_fpo_instead_view + for portion of valid_at from '2015-01-01' to '2020-01-01' + set b = 99 where id = '[9,9]'; -- error, even with no matching rows + +delete from uv_fpo_instead_view + for portion of valid_at from '2017-01-01' to '2022-01-01' + where id = '[9,9]'; -- error, even with no matching rows + +drop view uv_fpo_instead_view; +drop function uv_fpo_instead_trig(); -- 2.47.3