From 8defe6de6f69c842ade84b9d6ac7596cf4a21283 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Tue, 7 Jul 2026 17:21:34 -0700
Subject: [PATCH v7 2/3] Check FOR PORTION OF against INSTEAD OF triggers
 during rewriting

Move the INSTEAD OF trigger check to the rewriter. This loses the error
position but gives the check better locality with where we look up the
triggers.

Discussion: https://www.postgresql.org/message-id/CAJ7c6TME%2Bix6VRf-2TPnVTsj8qn_hy6sYAOmMhZEivwsu2wS6g%40mail.gmail.com
---
 src/backend/parser/analyze.c                  | 11 -----------
 src/backend/rewrite/rewriteHandler.c          |  8 ++++++++
 src/test/regress/expected/updatable_views.out |  8 --------
 3 files changed, 8 insertions(+), 19 deletions(-)

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 1e6fbc95964..2932d17a107 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1344,17 +1344,6 @@ 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/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e7ae9cce65f..38f54b57eec 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -4171,6 +4171,14 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length,
 		 */
 		rt_entry_relation = relation_open(rt_entry->relid, NoLock);
 
+		/* We don't support FOR PORTION OF on views with INSTEAD OF triggers. */
+		if (parsetree->forPortionOf &&
+			rt_entry_relation->rd_rel->relkind == RELKIND_VIEW &&
+			view_has_instead_trigger(rt_entry_relation, event, NIL))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("views with INSTEAD OF triggers do not support FOR PORTION OF")));
+
 		/*
 		 * Rewrite the targetlist as needed for the command type.
 		 */
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index acab165ae4c..93af96b7ddd 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -4179,27 +4179,19 @@ 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();
-- 
2.47.3

