agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
88+ messages / 1 participants
[nested] [flat]

* [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap.
@ 2026-03-09 10:35  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-03-09 10:35 UTC (permalink / raw)

It should make the copying more efficient. Besides that, buffer access
strategy is involved this way.

This diff reverts the changes done in previous parts of the series to
reform_and_rewrite_tuple() and introduces a separate function
heap_insert_for_repack().
---
 src/backend/access/heap/heapam_handler.c | 110 +++++++++++++++--------
 1 file changed, 72 insertions(+), 38 deletions(-)

diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f4c62e5bc7a..50ddee9d8c8 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -49,6 +49,11 @@
 static void reform_and_rewrite_tuple(HeapTuple tuple,
 									 Relation OldHeap, Relation NewHeap,
 									 Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+								   Relation NewHeap, Datum *values, bool *isnull,
+								   BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+							  Relation NewHeap, Datum *values, bool *isnull);
 
 static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
 								   HeapTuple tuple,
@@ -694,6 +699,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 								 double *tups_recently_dead)
 {
 	RewriteState rwstate = NULL;
+	BulkInsertState	bistate = NULL;
 	IndexScanDesc indexScan;
 	TableScanDesc tableScan;
 	HeapScanDesc heapScan;
@@ -729,6 +735,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	if (!concurrent)
 		rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
 									 *xid_cutoff, *multi_cutoff);
+	else
+		bistate = GetBulkInsertState();
 
 
 	/* Set up sorting if wanted */
@@ -958,8 +966,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 			};
 			int64		ct_val[2];
 
-			reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
-									 values, isnull, rwstate);
+			if (!concurrent)
+				reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+										 values, isnull, rwstate);
+			else
+				heap_insert_for_repack(tuple, OldHeap, NewHeap,
+									   values, isnull, bistate);
 
 			/*
 			 * In indexscan mode and also VACUUM FULL, report increase in
@@ -1007,10 +1019,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				break;
 
 			n_tuples += 1;
-			reform_and_rewrite_tuple(tuple,
-									 OldHeap, NewHeap,
-									 values, isnull,
-									 rwstate);
+			if (!concurrent)
+				reform_and_rewrite_tuple(tuple,
+										 OldHeap, NewHeap,
+										 values, isnull,
+										 rwstate);
+			else
+				heap_insert_for_repack(tuple, OldHeap, NewHeap,
+									   values, isnull, bistate);
+
 			/* Report n_tuples */
 			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
 										 n_tuples);
@@ -1022,6 +1039,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	/* Write out any remaining tuples, and fsync if needed */
 	if (rwstate)
 		end_heap_rewrite(rwstate);
+	if (bistate)
+		FreeBulkInsertState(bistate);
 
 	/* Clean up */
 	pfree(values);
@@ -2414,56 +2433,71 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
  * SET WITHOUT OIDS.
  *
  * So, we must reconstruct the tuple from component Datums.
- *
- * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that
- * case we still need to deform/form the tuple. TODO Shouldn't we rename the
- * function, as might not do any rewrite?
  */
 static void
 reform_and_rewrite_tuple(HeapTuple tuple,
 						 Relation OldHeap, Relation NewHeap,
 						 Datum *values, bool *isnull, RewriteState rwstate)
+{
+	HeapTuple       copiedTuple;
+
+	copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+	/* The heap rewrite module does the rest */
+	rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+	heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+					   Datum *values, bool *isnull, BulkInsertState bistate)
+{
+	tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+	heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+				HEAP_INSERT_NO_LOGICAL, bistate);
+
+	heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+			 Datum *values, bool *isnull)
 {
 	TupleDesc	oldTupDesc = RelationGetDescr(OldHeap);
 	TupleDesc	newTupDesc = RelationGetDescr(NewHeap);
-	HeapTuple	copiedTuple;
 	int			i;
 
 	heap_deform_tuple(tuple, oldTupDesc, values, isnull);
 
-	/* Be sure to null out any dropped columns */
 	for (i = 0; i < newTupDesc->natts; i++)
 	{
 		if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
 			isnull[i] = true;
 	}
 
-	copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
-	if (rwstate)
-		/* The heap rewrite module does the rest */
-		rewrite_heap_tuple(rwstate, tuple, copiedTuple);
-	else
-	{
-		/*
-		 * Insert tuple when processing REPACK CONCURRENTLY.
-		 *
-		 * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
-		 * difficult to do the same in the catch-up phase (as the logical
-		 * decoding does not provide us with sufficient visibility
-		 * information). Thus we must use heap_insert() both during the
-		 * catch-up and here.
-		 *
-		 * The following is like simple_heap_insert() except that we pass the
-		 * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps
-		 * the relation files, it drops this relation, so no logical
-		 * replication subscription should need the data.
-		 */
-		heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true),
-					HEAP_INSERT_NO_LOGICAL, NULL);
-	}
-
-	heap_freetuple(copiedTuple);
+	return heap_form_tuple(newTupDesc, values, isnull);
 }
 
 /*
-- 
2.47.3


--k4rn5ob3nx2ufkij--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread

* [PATCH 2/2] Provide the executor with information on updated columns.
@ 2026-07-01 13:18  Antonin Houska <[email protected]>
  0 siblings, 0 replies; 88+ messages in thread

From: Antonin Houska @ 2026-07-01 13:18 UTC (permalink / raw)

When replaying data changes, REPACK calls ExecInsertIndexTuples() for each
INSERT and UPDATE. In the latter case, the function needs to determine the
value of the 'indexUnchanged' hint for the index AM. (The hint is always false
for INSERT.) Therefore, REPACK is supposed to specify which columns are
changed by the UPDATE.

So far, REPACK missed to specify the set of updated columns, so the
'indexUnchanged' hint could incorrectly evaluate to true. Although it should
not affect correctness, it can make the index AM use optimizations that are
not appropriate.  Ideally, we should compute the set of updated columns for
each individual UPDATE, however the comparison of the old and new tuple might
add too much overhead.

This patch initializes the set as if all columns were updated. Thus
'indexUnchanged' always evaluates to false. This way the index AM never uses
the related optimizations. It might result in worse structure of the index,
however it seems better to not use the optimizations than to misuse them.
---
 src/backend/commands/repack.c | 40 ++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index c9b0c047477..f37bb9a21af 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
 #include "storage/bufmgr.h"
@@ -3005,13 +3006,50 @@ static void
 initialize_change_context(ChangeContext *chgcxt,
 						  Relation relation, Oid ident_index_id)
 {
+	Bitmapset	*updatedCols = NULL;
+	RangeTblEntry *rte;
+	List	   *perminfos = NIL;
+	RTEPermissionInfo *perminfo;
+
 	chgcxt->cc_rel = relation;
 
 	/* Only initialize fields needed by ExecInsertIndexTuples(). */
 	chgcxt->cc_estate = CreateExecutorState();
 
+	/*
+	 * Initialize updatedCols.
+	 *
+	 * The point is that ExecInsertIndexTuples() should not pass the
+	 * indexUnchanged hint to the index AM unless there's a reason to do
+	 * so. For simplicity, we consider all columns updated.
+	 *
+	 * XXX Should we spend more effort to compare the old and new tuple when
+	 * replaying UPDATE, or at least exclude unchanged TOAST values, like we
+	 * do in logicalrep_write_tuple()?
+	 */
+	for (int i = 0; i < RelationGetDescr(relation)->natts; i++)
+		updatedCols = bms_add_member(updatedCols,
+									 i + 1 - FirstLowInvalidHeapAttributeNumber);
+
+	/*
+	 * In this case, RTE only needs to have ->perminfoindex initialized, but
+	 * there's no reason to not set the fields whose values we have at hand.
+	 */
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(relation);
+	rte->relkind = RelationGetForm(relation)->relkind;
+	/* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
+	addRTEPermissionInfo(&perminfos, rte);
+	/* Make updatedCols available to the executor functions. */
+	perminfo = getRTEPermissionInfo(perminfos, rte);
+	perminfo->updatedCols = updatedCols;
+
+	ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
+					   bms_make_singleton(1));
+
 	chgcxt->cc_rri = makeNode(ResultRelInfo);
-	InitResultRelInfo(chgcxt->cc_rri, relation, 0, NULL, 0);
+	InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
 	ExecOpenIndices(chgcxt->cc_rri, false);
 
 	/*
-- 
2.52.0


--=-=-=--





^ permalink  raw  reply  [nested|flat] 88+ messages in thread


end of thread, other threads:[~2026-07-01 13:18 UTC | newest]

Thread overview: 88+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-03-09 10:35 [PATCH v40 4/4] Use BulkInsertState when copying data to the new heap. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>
2026-07-01 13:18 [PATCH 2/2] Provide the executor with information on updated columns. Antonin Houska <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox