agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting 38+ messages / 2 participants [nested] [flat]
* [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 38+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --r2slln3zpmilwu22-- ^ permalink raw reply [nested|flat] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ 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; 38+ 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] 38+ messages in thread
end of thread, other threads:[~2026-07-01 13:18 UTC | newest] Thread overview: 38+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-04-03 19:01 [PATCH v51 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <[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