From 0576a833fb679d5d613a460824e5afd5993df98a Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Fri, 5 Dec 2025 13:42:13 -0500
Subject: [PATCH v29 4/4] Identify if partial indexes are impacted by an
 update.

The executor now determines which, if any, attributes that are indexed
are both modified and force new index tuples to be inserted ahead of
calling into the table AM update function.  Prior to this commit the
test for partial indexes happened after table update, this changes that
to before so that in cases where the before and after tuples both lie
outside the predicate the attributes for the predicate are not included
in the "modified indexed attributes" bitmapset.
---
 src/backend/executor/nodeModifyTable.c | 53 ++++++++++++++++++++++++--
 1 file changed, 49 insertions(+), 4 deletions(-)

diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 22b1ca25ebf..5439dc46b56 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -227,9 +227,11 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		Bitmapset  *m_attrs = NULL; /* (possibly) modified indexed attrs */
 		Bitmapset  *p_attrs = NULL; /* (possibly) modified predicate attrs */
 		Bitmapset  *u_attrs = NULL; /* unmodified indexed attrs */
+		Bitmapset  *pre_attrs = indexInfo->ii_PredicateAttrs;
 		bool		has_am_compare = (amroutine->amcomparedatums != NULL);
 		bool		supports_ios = (amroutine->amcanreturn != NULL);
 		bool		is_partial = (indexInfo->ii_Predicate != NIL);
+		TupleTableSlot *save_scantuple;
 		ExprContext *econtext = GetPerTupleExprContext(estate);
 		int			num_datums = supports_ios ?
 			indexInfo->ii_NumIndexAttrs : indexInfo->ii_NumIndexKeyAttrs;
@@ -238,9 +240,51 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 		if (bms_is_subset(indexInfo->ii_IndexedAttrs, mix_attrs))
 			continue;
 
-		/* Add partial index attributes */
-		if (is_partial)
-			p_attrs = bms_add_members(p_attrs, indexInfo->ii_PredicateAttrs);
+		/* Checking partial at this point isn't viable when we're serializable */
+		if (is_partial && IsolationIsSerializable())
+		{
+			p_attrs = bms_add_members(p_attrs, pre_attrs);
+		}
+		/* Check partial index predicate */
+		else if (is_partial)
+		{
+			ExprState  *pstate;
+			bool		old_qualifies,
+						new_qualifies;
+
+
+			if (!indexInfo->ii_CheckedPredicate)
+				pstate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+			else
+				pstate = indexInfo->ii_PredicateState;
+
+			save_scantuple = econtext->ecxt_scantuple;
+
+			econtext->ecxt_scantuple = old_tts;
+			old_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = new_tts;
+			new_qualifies = ExecQual(pstate, econtext);
+
+			econtext->ecxt_scantuple = save_scantuple;
+
+			indexInfo->ii_CheckedPredicate = true;
+			indexInfo->ii_PredicateState = pstate;
+			indexInfo->ii_PredicateSatisfied = new_qualifies;
+
+			/* Both outside predicate, index doesn't need update */
+			if (!old_qualifies && !new_qualifies)
+				continue;
+
+			/* A transition means we need to update the index */
+			if (old_qualifies != new_qualifies)
+				p_attrs = bms_copy(pre_attrs);
+
+			/*
+			 * When both are within the predicate we must update this index,
+			 * but only if one of the index key attributes changed.
+			 */
+		}
 
 		/* Compare the index datums for equality */
 		for (int j = 0; j < num_datums; j++)
@@ -276,11 +320,12 @@ ExecCheckIndexedAttrsForChanges(ResultRelInfo *relinfo,
 			 */
 			else if (rel_attrnum == 0)
 			{
-				TupleTableSlot *save_scantuple = econtext->ecxt_scantuple;
 				Oid			expr_type_oid;
 				Expr	   *expr = (Expr *) list_nth(indexInfo->ii_Expressions, nth_expr);
 				ExprState  *state;
 
+				save_scantuple = econtext->ecxt_scantuple;
+
 				if (indexInfo->ii_ExpressionsState == NIL)
 				{
 					/* First time through, set up expression evaluation state */
-- 
2.51.2

